以下为一对 TCP 客户和服务器进程之间发生的一些典型事件的时间表。
服务器首先启动,稍后某个客户启动,它试图连接到服务器。我们假设客户给服务器发送一个请求,服务器处理该请求,并给客户发回一个响应。这个过程持续下去,直至客户关闭连接的客户端,从而给服务器发送一个 EOF (文件结束) 通知为止。服务器接着也关闭连接的服务器端,然后结束运行或等待新的客户连接。
(以上资链接:https://blog.csdn.net/YangLei253/article/details/90748313)
1、socket()函数
在进行网络I/O时,一个进程最先调用的就是socket()函数,指定期望的通信协议。
1 #include <sys/types.h> /* See NOTES */ 2 #include <sys/socket.h> 3 4 int socket(int domain, int type, int protocol);
参数:
1)domain:协议族/协议域
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_NETLINK Kernel user interface device netlink(7)
AF_PACKET Low level packet interface packet(7)
2) type:套接字类型
SOCK_STREAM:流式套接字,唯一对应TCP
SOCK_DGRAM:数据包套接字,唯一对应UDP
SOCK_RAW:原始套接字
3)protocol:一般填0,原始套接字编程时需填充
返回值:
成功返回非负整数,即套接字描述符,类似于文件描述符,出错返回-1
2、bind()函数
1 #include <sys/types.h> /* See NOTES */ 2 #include <sys/socket.h> 3 //bind将一个本地协议地址赋予一个套接字。对于网际网协议,协议地址是 32位 IPv4 地址或 128 位 IPv6 地址与 16 位的 TCP 或 UDP 端口号组合。 4 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd :待连接的sockfd,通过socket获得
addr :本地的通用套接字地址结构
addrlen :地址长度
1)sockaddr
struct sockaddr 是一个通用地址结构,这是为了统一地址结构的表示方法,统一接口函数,使不同的地址结构可以被bind() , connect() 等函数调用;但缺陷在于sa_data把目标地址和端口信息混在一起
1 struct sockaddr { 2 unsigned short sa_family; //地址族 3 char sa_data[14]; //14字节,包含套接字中的目标地址与端口信息 4 };
2)sockaddr_in
struct sockaddr_in中的in 表示internet,即网络地址,为比较常用的地址结构,属于AF_INET地址族,
sockaddr_in结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
1 struct sockaddr_in { 2 short int sin_family; //地址族 NBD 3 unsigned short int sin_port; //16位TCP/UDP端口号 4 struct in_addr sin_addr; //32位IP地址 5 unsigned char sin_zero[8]; //不使用 6 } 7 8 struct in_addr { 9 unsigned long s_addr; //32位IPV4地址 10 }
11 sin_zero 初始值应该使用函数 bzero() 来全部置零。
sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。
sockaddr_in结构体变量的基本配置
struct sockaddr_in ina; bzero(&ina,sizeof(ina)); ina.sin_family=AF_INET; ina.sin_port=htons(23); ina.sin_addr.s_addr = inet_addr("132.241.5.10"); |
3)两者相互关系
sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。
sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以。 一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。
3、listen()函数
listen 函数仅由 TCP 服务器调用,当 socket 函数创建一个套接字时,默认为一个主动套接字,也就是说它是一个将调用 connect 发起连接的客户套接字。
listen 函数把一个未连接的套接字转换为一个被动套接字,指示内核应该接收指向该套接字的连接请求。
1 #include<sys/socket.h> 2 #include<sys/types.h> 3 int listen(int sockfd,int backlog); 4 //第二个参数规定内核应该为相应套接字排队的最大连接个数
参数:
sockfd:通过socket函数拿到的fd
backlog:同时允许几路客户端和服务器进行正在连接的过程(正在三次握手)
一般填5,测试可知,ARM最大为8
注:内核中服务器的套接字fd会维护2个链表:
1、正在三次握手的客户端链表(数量 = 2*backlog + 1)
2、已经建立好连接的客户端链表(已经完成三次握手,分配好了newfd)
eg:listen(fd, 5); //表示系统允许11 = 2*5 + 1个客户端进行三次握手
返回值:
成功返回0,出错返回-1
4、accept()函数
阻塞等待客户端连接请求
1 #include <sys/types.h> /* See NOTES */ 2 #include <sys/socket.h> 3 //等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字 4 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:经过前面socket()创建并通过bind(),listen()设置的fd
addr和addrlen
返回值:
成功返回新建的newfd, 失败返回-1
5、客户端的连接函数 connect()
客户端使用 connect 函数来建立与 TCP 服务器的连接。
1 #include <sys/socket。h> 2 #include <sys/types.h> 3 4 int connect(int sockfd,const struct sockaddr *servaddr,socklent_t addrlen);
connect()和服务端的bind()函数写法基本一致。
当服务器的监听地址是INADDR_ANY时,意思不是监听所有的客户端IP。而是服务器端的IP地址可以随意配置,这样使得该服务器端程序可以运行在任意计算机上,可使任意计算机作为服务器,便于程序移植。将INADDR_ANY换成127.0.0.1也可以达到同样的目的。这样,当作为服务器的计算机的IP有变动或者网卡数量有增减,服务器端程序都能够正常监听来自客户端的请求。
测试:基于TCP协议的简单客户端与服务器交互
基于TCP(面向连接)的socket编程,分为客户端和服务器端。
客户端的流程如下:
(1)创建套接字(socket)
(2)向服务器发出连接请求(connect)
(3)和服务器端进行通信(send/recv)
(4)关闭套接字
服务器端的流程如下:
(1)创建套接字(socket)
(2)将套接字绑定到一个本地地址和端口上(bind)
(3)将套接字设为监听模式,准备接收客户端请求(listen)
(4)等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)
(5)用返回的套接字和客户端进行通信(send/recv)
(6)返回,等待另一个客户请求。
(7)关闭套接字。
1 #include "net.h" 2 3 int main() 4 { 5 int fd = -1; 6 struct sockaddr_in sin; //Internet环境下套接字的地址形式,对其进行操作以建立信息 7 8 /*1、创建套接字描述符fd */ 9 if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) 10 { 11 perror("socket"); 12 exit(1); 13 } 14 15 /*2、将套接字绑定到一个本地地址与端口上*/ 16 17 /*2.1 填充struct sockaddr_in结构体变量*/ 18 bzero(&sin, sizeof(sin)); //初始值置零 19 //sin_port和sin_addr都必须是NBD,且可视化的数字一般都是HBD,如端口号23 20 sin.sin_family = AF_INET; //协议,ipv4 21 sin.sin_port = htons(SERV_PORT); //将端口号转化为NBD 22 23 /*优化1:让服务器能绑定在任意IP上*/ 24 sin.sin_addr.s_addr = htonl(INADDR_ANY); 25 26 /*2.2 绑定 */ 27 if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) 28 { 29 perror("bind"); 30 exit(1); 31 } 32 33 /*3. 把套接字设为监听模式,准备接收客户请求*/ 34 if(listen(fd, BACKLOG) < 0) 35 { 36 perror("listen"); 37 exit(1); 38 } 39 printf("Severing start...OK!\n"); 40 41 /*4.等待客户请求到来,当请求到来后,接收请求,返回一个基于此次的新的套接字 */ 42 int newfd = -1; 43 44 /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址与端口号 */ 45 struct sockaddr_in cin; 46 socklen_t addrlen = sizeof(cin); 47 //获取客户端信息 48 if((newfd = accept(fd, (struct sockaddr *)&cin, &addrlen))<0) 49 { 50 perror("accept"); 51 exit(1); 52 } 53 //读出客户端信息,并将HBD转为NBD 54 char ipv4_addr[16]; 55 if(!inet_ntop(AF_INET, (void *)&cin.sin_addr,ipv4_addr,sizeof(cin))) 56 { 57 perror("inet_ntop"); 58 exit(1); 59 } 60 //打印客户端的IP和端口号 61 printf("Client(%s,%d) is connected!\n",ipv4_addr,ntohs(cin.sin_port)); 62 63 64 /*5. 读写数据*/ 65 int ret = -1; 66 char buf[BUFSIZ]; 67 68 while(1) 69 { 70 bzero(buf, BUFSIZ); 71 do{ 72 ret = read(newfd, buf, BUFSIZ-1); 73 }while(ret < 0 && EINTR == errno); 74 if(ret < 0) 75 { 76 perror("read"); 77 exit(1); 78 } 79 80 if(!ret) //对方已经关闭 81 { 82 break; 83 } 84 printf("Receive data: %s",buf); 85 86 if(!strncasecmp(buf,QUIT_STR, strlen(QUIT_STR))) 87 { 88 printf("Client is exiting!\n"); 89 break; 90 } 91 } 92 close(newfd); 93 close(fd); 94 return 0; 95 96 }
1 #include "net.h" 2 3 int main() 4 { 5 int fd = -1; 6 struct sockaddr_in sin; 7 8 /*1.创建sock fd */ 9 if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) 10 { 11 perror("socket"); 12 exit(1); 13 } 14 15 /*2.连接服务器 */ 16 /*2.1 填充struct sockaddr_in结构体变量*/ 17 bzero(&sin, sizeof(sin)); //初始值置零 18 sin.sin_family = AF_INET; // 19 sin.sin_port = htons(SERV_PORT); //转化为NBD 20 #if 0 21 sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); 22 #else 23 if(inet_pton(AF_INET, SERV_IP_ADDR,(void *)&sin.sin_addr.s_addr) != 1) 24 { 25 perror("inet_pton"); 26 exit(1); 27 } 28 #endif 29 30 if(connect(fd,(struct sockaddr *)&sin, sizeof(sin)) < 0) 31 { 32 perror("connect"); 33 exit(1); 34 } 35 36 printf("Client starting ...\n"); 37 38 /*3.读写数据*/ 39 char buf[BUFSIZ]; 40 int ret = -1; 41 while(1) 42 { 43 bzero(buf,BUFSIZ); 44 if(fgets(buf, BUFSIZ-1, stdin) == NULL) 45 { 46 continue; 47 } 48 do{ 49 ret = write(fd, buf, strlen(buf)); 50 }while(ret < 0 && EINTR == errno); 51 if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR))) 52 { 53 printf("Clinet is exiting!\n"); 54 break; 55 } 56 57 } 58 59 /*4.关闭套接字 */ 60 close(fd); 61 62 return 0; 63 }
1 #ifndef __MAKEU_NET_H__ 2 #define __MAKEU_NET_H__ 3 #include <stdio.h> 4 #include <string.h> 5 #include <strings.h> 6 #include <stdlib.h> 7 #include <unistd.h> 8 #include <sys/types.h> 9 #include <sys/socket.h> 10 #include <errno.h> 11 #include <netinet/in.h> 12 #include <netinet/ip.h> 13 14 #define BACKLOG 5 15 #define QUIT_STR "quit" 16 #define SERV_PORT 5001 17 #define SERV_IP_ADDR "192.168.31.123" 18 19 #endif
测试结果: