Socket
在 UNIX/Linux 系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。
UNIX/Linux 程序在执行任何形式的 I/O 操作时,都是在读取或者写入一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数,它的背后可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。注意:网络连接也是一个文件,它也有文件描述符。
我们可以通过socket()函数来创建一个网络连接,socket()函数的返回值就是文件描述符。有了文件描述符,我们就可以用read()读取从远程计算机传来的数据,用write()向远程计算机写入数据。
socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,在设计模式中就是一个门面模式。1.所谓socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的语抦。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。2.Socket是连接运行在网络上的两个程序间的双向通信的端点。3.网络通讯其实指的就是Socket间的通讯。通讯的两端都有Socket,数据在两个Socket之间通过IO来进行传输。
image.png
image.png
socket类型
流格式套接字(SOCK_STREAM)
流格式套接字(SOCK_STREAM)也叫“面向连接的套接字”,它使用TCP协议,从而保证了数据传输的正确性和顺序性。它有以下几个特征:1、数据在传输过程中不会丢失。2、数据是按照顺序传输的。3、数据的发送和接受不是同步的。
数据报格式套接字(SOCK_DGRAM)
数据报格式套接字(Datagram Sockets)也叫“无连接的套接字”,它使用UDP协议,一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它有以下几个特征:1、强调快速传输而非传输顺序。2、传输的数据可能丢失也可能损毁。3、限制每次传输数据的大小。4、数据的发送和接受是同步的。
Linux下socket程序 这里只写了基于TCP的socket代码
服务端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <errno.h>
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
int server_sockfd; //服务端套接字
int client_sockfd; //客户端套接字
int len;
struct sockaddr_in my_add; //服务器网络地址结构体
struct sockaddr_in remote_addr; //客户端网络地址结构体
socklen_t sin_size;
char buf[BUFSIZ]; //数据传送的缓冲区
memset(&my_add,0,sizeof(my_add)); //数据初始化--清零
my_add.sin_family = AF_INET; //设置为IP通信
my_add.sin_addr.s_addr = INADDR_ANY; //服务器IP地址--允许连接到所有本地地址上
my_add.sin_port = htons(8000); //服务器端口号
//创建服务器端套接字--IPV4协议 ,面向连接通信,TCP协议
if((server_sockfd=socket(PF_INET, SOCK_STREAM, 0))<0) {
perror("socket");
return 1;
}
//将套接字绑定到服务器的网络地址上
if (bind(server_sockfd, (struct sockaddr *)&my_add, sizeof(struct sockaddr))<0) {
perror("bind");
return 1;
}
//监听连接请求--监听队列长度为5
listen(server_sockfd, 5);
sin_size = sizeof(struct sockaddr_in);
//等待客户端连接请求到达
if ((client_sockfd=accept(server_sockfd, (struct sockaddr *)&remote_addr, &sin_size))<0) {
perror("accept");
return 1;
}
printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));
len = send(client_sockfd, "welcome to my server\n", 21, 0); //发送欢迎信息
//接受客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数
while ((len==recv(client_sockfd, buf, BUFSIZ, 0))>0) {
buf[len]='\0';
printf("receive:%s\n",buf);
if (send(client_sockfd, buf, len, 0)<0) {
perror("write");
return 1;
}
}
close(client_sockfd);
close(server_sockfd);
return 0;
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <netdb.h>
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
int client_sockfd;
int len;
struct sockaddr_in remota_addr; //服务器端网络地址结构体
char buf[BUFSIZ]; //数据传送的缓冲区
memset(&remota_addr,0,sizeof(remota_addr)); //数据初始化--清零
remota_addr.sin_family = AF_INET; //设置为IPV4通信
remota_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
remota_addr.sin_port = htons(8000); //服务器端口号
//创建客户端套接字--Ipv4协议,面向连接通信,TCP协议
//成功,返回0 ,失败返回-1
if ((client_sockfd=socket(PF_INET, SOCK_STREAM, 0))<0) {
perror("socket");
return 1;
}
//将套接字绑定到服务器的网络地址上
if (connect(client_sockfd, (struct sockaddr *)&remota_addr, sizeof(struct sockaddr))<0) {
perror("connect");
return 1;
}
printf("connect to server\n");
len = recv(client_sockfd, buf, BUFSIZ, 0); //接受服务器端消息
buf[len]='/0';
printf("%s",buf); //打印服务器端消息
//循环的发送信息并打印接受消息--recv返回接收到的字节数,send返回发送的字节数
while (1) {
printf("Enter string to send");
scanf("%s",buf);
if (!strcmp(buf,"quit")) {
break;
}
len=send(client_sockfd,buf,strlen(buf),0);
len=recv(client_sockfd,buf,BUFSIZ,0);
buf[len]='/0';
printf("received:%s\n",buf);
}
close(client_sockfd);
return 0;
}
在 Linux 下使用 <sys/socket.h> 头文件中 socket() 函数来创建套接字。
int socket(int af, int type, int protocol);
(1)、af为地址族(Address Family),也就是IP地址类型,常用的有AF_INET 和 AF_INET6。AF_INET 表示 IPv4 地址,AF_INET6 表示 IPv6 地址。也可以使用 PF 前缀(PF_INET 等价于 AF_INET,PF_INET6 等价于 AF_INET6)。
(2)、type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字)和 SOCK_DGRAM(数据报套接字/无连接的套接字)。
(3)、protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。
这种套接字称为 TCP 套接字:
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //IPPROTO_TCP表示TCP协议
这种套接字称为 UDP 套接字:
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //IPPROTO_UDP表示UDP协议
也可以写成:
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字
bind()函数
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
int server_sockfd; //服务端套接字
struct sockaddr_in my_add; //服务器网络地址结构体
char buf[BUFSIZ]; //数据传送的缓冲区
memset(&my_add,0,sizeof(my_add)); //数据初始化--清零
my_add.sin_family = AF_INET; //设置为IP通信
my_add.sin_addr.s_addr = INADDR_ANY; //服务器IP地址--允许连接到所有本地地址上
my_add.sin_port = htons(8000); //服务器端口号
//创建服务器端套接字--IPV4协议 ,面向连接通信,TCP协议
if((server_sockfd=socket(PF_INET, SOCK_STREAM, 0))<0) {
perror("socket");
return 1;
}
//将套接字绑定到服务器的网络地址上
if (bind(server_sockfd, (struct sockaddr *)&my_add, sizeof(struct sockaddr))<0) {
perror("bind");
return 1;
}
sockaddr_in 结构体:
struct sockaddr_in{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
uint16_t sin_port; //16位的端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用,一般用0填充
};
(1)、sin_family 和 socket() 的第一个参数的含义相同,取值也要保持一致。
(2)、sin_prot 为端口号。
(3)、sin_addr 是 struct in_addr 结构体类型的变量
(4)、sin_zero[8] 是多余的8个字节,没有用,一般使用 memset() 函数填充为 0。
in_addr 结构体
struct in_addr{
in_addr_t s_addr; //32位的IP地址
};
in_addr_t 在头文件 <netinet/in.h> 中定义,等价于 unsigned long,长度为4个字节。也就是说,s_addr 是一个整数,而IP地址是一个字符串,所以需要 inet_addr() 函数进行转换,例如:unsigned long ip = inet_addr("127.0.0.1");
connect() 函数:connect() 函数用来建立连接
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
各个参数的说明和 bind() 相同。
listen() 函数:通过 listen() 函数可以让套接字进入被动监听状态
int listen(int sock, int backlog);
sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。
请求队列:当套接字正在处理客户端的请求时,如果这个时候有新的请求进来,套接字是没法处理的,只能把它放在缓冲区,直到当前请求处理完毕后,再从缓冲区读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区就成为请求队列。缓冲区的大小可以通过listen()函数的backlog参数指定。如果设置为SOMAXCONN,就由系统来决定请求队列长度。
listen() 只是让套接字处于监听状态,并没有接收请求。
accept() 函数:通过 accept() 函数来接收客户端请求
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);·
sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。
accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。
write()函数:
ssize_t write(int fd, const void *buf, size_t nbytes);
fd 为要写入的文件的描述符,buf 为要写入的数据的缓冲区地址,nbytes 为要写入的数据的字节数。
read()函数:
ssize_t read(int fd, void *buf, size_t nbytes);
fd 为要读取的文件的描述符,buf 为要接收数据的缓冲区地址,nbytes 为要读取的数据的字节数。
网友评论