Linux高并发网络编程开发——tcp状态转换-select-poll
在学习Linux高并发网络编程开发总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。
10-Linux系统编程-第12天(tcp状态转换-select-poll)
目录:
一、学习目标
二、复习
三、TCP状态转换
1、recv和send函数
2、TCP状态转换
3、2MSL等待时长
4、半关闭(了解)
5、netstat命令
6、端口复用设置
7、IO多路转接
8、内核大致是如何实现IO转接的
9、select的参数和返回值
10、select工作过程
11、select伪代码
12、select代码实现
13、poll函数介绍
14、poll实现IO转接代码分析
一、学习目标
1、能够描述TCP通信过程中主要状态
2、独立使用select实现IO多路转接
3、理解使用poll实现IO多路转接操作流程
二、复习
(上篇是客户端主动断开连接,此图是服务器端主动断开连接,二者皆可以!)
三、TCP状态转换
1、recv和send函数
相对于read和write,还有recv和send函数,对比如下:
2、TCP状态转换
(注意:需要左右图对比看,其中红线为Client端的状态转换,虚线为Server端的状态转换。)
右图中有些状态可以捕捉到,有些捕捉不到,可通过netstat命令查看,参看——5、nestat命令
3、2MSL等待时长
2MSL大约就是30s * 2 = 60s
4、半关闭(了解)
一般使用close即可,用不到shutdown半关闭,除非做了文件描述符重定向、文件描述符复制,需要半关闭。
5、netstat命令
(打开多个终端,一个终端开启服务器进程,另几个终端开启客户端进程,然后打开另一个终端,输入:netstat -apn | grep 端口号,查看所有的某个端口号的网络状态信息(如:ESTABLISHED,COLSE_WAIT,FIN_WAIT2)。)
6、端口复用设置
》问题:当服务器端主动断开连接(Ctrl+c),然后再次运行服务器端程序,运行不了,如果检测端口,发现:bind error: Address already in use(端口仍被占用)?
》分析:主动断开的一方有个断开时长MSL,1分钟后再次启动就会正常。
》有没有更好的解决方案呢?端口复用
具体setsockopt 函数及参数参看《Unix网络编程》第7章7.2!
测试(server.c)
>touch server.c
>vi server.c
1 #include <stdio.h> 2 #include <ctype.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 #include <sys/types.h> 6 #include <sys/stat.h> 7 #include <string.h> 8 #include <arpa/inet.h> 9 #include <sys/socket.h> 10 11 // server 12 int main(int argc, const char* argv[]) 13 { 14 // 创建监听的套接字 15 int lfd = socket(AF_INET, SOCK_STREAM, 0); 16 if(lfd == -1) 17 { 18 perror("socket error"); 19 exit(1); 20 } 21 22 // 绑定 23 struct sockaddr_in serv_addr; 24 memset(&serv_addr, 0, sizeof(serv_addr)); 25 serv_addr.sin_family = AF_INET; 26 serv_addr.sin_port = htons(9999); 27 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本地多有的IP 28 // 127.0.0.1 29 // inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr); 30 31 // 设置端口复用 32 // 需要在bind函数之前设置 33 int opt = 1; 34 setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt)); 35 36 // 绑定端口 37 int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 38 if(ret == -1) 39 { 40 perror("bind error"); 41 exit(1); 42 } 43 44 // 监听 45 ret = listen(lfd, 64); 46 if(ret == -1) 47 { 48 perror("listen error"); 49 exit(1); 50 } 51 52 // 阻塞等待连接请求, 并接受连接请求 53 struct sockaddr_in clien_addr; 54 socklen_t clien_len = sizeof(clien_addr); 55 int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len); 56 if(cfd == -1) 57 { 58 perror("accetp error"); 59 exit(1); 60 } 61 62 char ipbuf[128]; 63 printf("client iP: %s, port: %d\n", inet_ntop(AF_INET, &clien_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)), 64 ntohs(clien_addr.sin_port)); 65 66 char buf[1024] = {0}; 67 while(1) 68 { 69 // read data 70 // int len = read(cfd, buf, sizeof(buf)); 71 int len = recv(cfd, buf, sizeof(buf), 0); 72 if(len == -1) 73 { 74 perror("recv error"); 75 exit(1); 76 } 77 else if(len == 0) 78 { 79 printf("客户端已经断开连接。。。\n"); 80 break; 81 } 82 printf("read buf = %s\n", buf); 83 // 小写转大写 84 for(int i=0; i<len; ++i) 85 { 86 buf[i] = toupper(buf[i]); 87 } 88 printf("after buf = %s\n", buf); 89 90 // 大写串发给客户端 91 // write(cfd, buf, strlen(buf)+1); 92 ret = send(cfd, buf, strlen(buf)+1, 0); 93 if(ret == -1) 94 { 95 perror("send error"); 96 exit(1); 97 } 98 } 99 100 close(cfd); 101 close(lfd); 102 103 return 0; 104 105 }
>gcc server.c -o server
>./server
(这时候在server的终端Ctrl+c结束,立马就可以再次运行)
7、IO多路转接
》IO操作方式
》解决方案?
8、内核大致是如何实现IO转接的
9、select的参数和返回值
》为什么文件描述符最大为1024位?
fd_set内部数组实现的代码片段如下:
》文件描述符操作函数:(掌握函数调用)
10、select工作过程
11、select伪代码
》select多路转接伪代码:
1 int main() 2 { 3 int lfd = socket(); 4 bind(); 5 listen(); 6 7 //创建- 文件描述符表(temp做备份用) 8 fd_set reads, temp; 9 //初始化 10 fd_zero(&reads); 11 //监听的lfd加入到读集合 12 fd_set(lfd, &reads); 13 int maxfd = lfd;//maxfd初始化 14 15 while(1) 16 { 17 //委托检测 18 temp = reads; 19 int ret = select(maxfd + 1, &temp, NULL, NULL, NULL); 20 21 //是不是监听的 22 if(fd_isset(lfd, &temp)) 23 { 24 //接受新连接 25 int cfd = accept(); 26 //cfd加入读集合 27 fd_set(cfd, &reads); 28 //更新maxfd 29 maxfd = maxfd < cfd ? cfd : maxfd; 30 } 31 //客户端发送数据,如果lfd没有数据,为3 32 for(int i = lfd + 1; i <= maxfd; ++i) 33 { 34 if(fd_isset(i, &temp)) 35 { 36 int len = read();//读数据 37 if(len == 0)//对方断开连接 38 { 39 //cfd从读集合中del 40 fd_clr(i, &reads); 41 } 42 write(); 43 } 44 } 45 46 } 47 48 }
12、select代码实现
>touch select.c
>vi select.c
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <sys/select.h> 10 11 int main(int argc, const char* argv[]) 12 { 13 if(argc < 2) 14 { 15 printf("eg: ./a.out port\n"); 16 exit(1); 17 } 18 struct sockaddr_in serv_addr; 19 socklen_t serv_len = sizeof(serv_addr); 20 int port = atoi(argv[1]); 21 22 // 创建套接字 23 int lfd = socket(AF_INET, SOCK_STREAM, 0); 24 // 初始化服务器 sockaddr_in 25 memset(&serv_addr, 0, serv_len); 26 serv_addr.sin_family = AF_INET; // 地址族 27 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP 28 serv_addr.sin_port = htons(port); // 设置端口 29 // 绑定IP和端口 30 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 31 32 // 设置同时监听的最大个数 33 listen(lfd, 36); 34 printf("Start accept ......\n"); 35 36 struct sockaddr_in client_addr; 37 socklen_t cli_len = sizeof(client_addr); 38 39 // 最大的文件描述符 40 int maxfd = lfd; 41 // 文件描述符读集合 42 fd_set reads, temp; 43 // init 44 FD_ZERO(&reads); 45 FD_SET(lfd, &reads); 46 47 while(1) 48 { 49 // 委托内核做IO检测 50 temp = reads; 51 int ret = select(maxfd+1, &temp, NULL, NULL, NULL); 52 if(ret == -1)//select调用失败,退出当前进程 53 { 54 perror("select error"); 55 exit(1); 56 } 57 // 客户端发起了新的连接 58 if(FD_ISSET(lfd, &temp)) 59 { 60 // 接受连接请求 - accept不阻塞 61 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 62 if(cfd == -1)//accept调用失败,退出当前进程 63 { 64 perror("accept error"); 65 exit(1); 66 } 67 char ip[64]; 68 printf("new client IP: %s, Port: %d\n", 69 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 70 ntohs(client_addr.sin_port)); 71 // 将cfd加入到待检测的读集合中 - 下一次就可以检测到了 72 FD_SET(cfd, &reads); 73 // 更新最大的文件描述符 74 maxfd = maxfd < cfd ? cfd : maxfd; 75 } 76 // 已经连接的客户端有数据到达 77 for(int i=lfd+1; i<=maxfd; ++i) 78 { 79 if(FD_ISSET(i, &temp))//必须判断是被内核修改过的表 80 { 81 char buf[1024] = {0}; 82 int len = recv(i, buf, sizeof(buf), 0); 83 if(len == -1)//recv调用失败,退出当前进程 84 { 85 perror("recv error"); 86 exit(1); 87 } 88 else if(len == 0) 89 { 90 printf("客户端已经断开了连接\n"); 91 close(i); 92 // 从读集合中删除 93 FD_CLR(i, &reads); 94 } 95 else 96 { 97 printf("recv buf: %s\n", buf);//接收数据 98 send(i, buf, strlen(buf)+1, 0);//数据发送出去 99 } 100 } 101 } 102 } 103 104 close(lfd); 105 return 0; 106 }
>gcc select.c -o select
>./select 9876
(打开另外两个终端,执行./client 9876,然后分别输入数据,看原server终端的接收情况)
只有一个进程,可以支持多客户端连接。
》poll内部fd_set是靠链表实现,无1024限制,epoll内部fd_set靠树实现,无1024限制。
》前两个缺点也是poll的。
13、poll函数介绍
14、poll实现IO转接代码分析
>touch poll.c
>vi poll.c
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <string.h> 6 #include <sys/socket.h> 7 #include <arpa/inet.h> 8 #include <ctype.h> 9 #include <poll.h> 10 11 #define SERV_PORT 8989 12 13 int main(int argc, const char* argv[]) 14 { 15 int lfd, cfd; 16 struct sockaddr_in serv_addr, clien_addr; 17 int serv_len, clien_len; 18 19 // 创建套接字 20 lfd = socket(AF_INET, SOCK_STREAM, 0); 21 // 初始化服务器 sockaddr_in 22 memset(&serv_addr, 0, sizeof(serv_addr)); 23 serv_addr.sin_family = AF_INET; // 地址族 24 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP 25 serv_addr.sin_port = htons(SERV_PORT); // 设置端口 26 serv_len = sizeof(serv_addr); 27 // 绑定IP和端口 28 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 29 30 // 设置同时监听的最大个数 31 listen(lfd, 36); 32 printf("Start accept ......\n"); 33 34 // poll结构体 35 struct pollfd allfd[1024]; 36 int max_index = 0; 37 // init 38 for(int i=0; i<1024; ++i) 39 { 40 allfd[i].fd = -1; 41 } 42 allfd[0].fd = lfd; 43 allfd[0].events = POLLIN; 44 45 while(1) 46 { 47 int i = 0; 48 int ret = poll(allfd, max_index+1, -1); 49 if(ret == -1) 50 { 51 perror("poll error"); 52 exit(1); 53 } 54 55 // 判断是否有连接请求 56 if(allfd[0].revents & POLLIN) 57 { 58 clien_len = sizeof(clien_addr); 59 // 接受连接请求 60 int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len); 61 printf("============\n"); 62 63 // cfd添加到poll数组 64 for(i=0; i<1024; ++i) 65 { 66 if(allfd[i].fd == -1) 67 { 68 allfd[i].fd = cfd; 69 break; 70 } 71 } 72 // 更新最后一个元素的下标 73 max_index = max_index < i ? i : max_index; 74 } 75 76 // 遍历数组 77 for(i=1; i<=max_index; ++i) 78 { 79 int fd = allfd[i].fd; 80 if(fd == -1) 81 { 82 continue; 83 } 84 if(allfd[i].revents & POLLIN) 85 { 86 // 接受数据 87 char buf[1024] = {0}; 88 int len = recv(fd, buf, sizeof(buf), 0); 89 if(len == -1) 90 { 91 perror("recv error"); 92 exit(1); 93 } 94 else if(len == 0) 95 { 96 allfd[i].fd = -1; 97 close(fd); 98 printf("客户端已经主动断开连接。。。\n"); 99 } 100 else 101 { 102 printf("recv buf = %s\n", buf); 103 for(int k=0; k<len; ++k) 104 { 105 buf[k] = toupper(buf[k]); 106 } 107 printf("buf toupper: %s\n", buf); 108 send(fd, buf, strlen(buf)+1, 0); 109 } 110 111 } 112 113 } 114 } 115 116 close(lfd); 117 return 0; 118 }
》select.c与poll.c代码对比
》扩展:代码对比工具——BCompare-4.0.7.19761
1 Beyond Compare 4 2 Licensed to: All User 3 Quantity: 1 user 4 Serial number: 1822-9597 5 License type: Pro Edition for Windows 6 7 --- BEGIN LICENSE KEY --- 8 H1bJTd2SauPv5Garuaq0Ig43uqq5NJOEw94wxdZTpU-pFB9GmyPk677gJ 9 vC1Ro6sbAvKR4pVwtxdCfuoZDb6hJ5bVQKqlfihJfSYZt-xVrVU27+0Ja 10 hFbqTmYskatMTgPyjvv99CF2Te8ec+Ys2SPxyZAF0YwOCNOWmsyqN5y9t 11 q2Kw2pjoiDs5gIH-uw5U49JzOB6otS7kThBJE-H9A76u4uUvR8DKb+VcB 12 rWu5qSJGEnbsXNfJdq5L2D8QgRdV-sXHp2A-7j1X2n4WIISvU1V9koIyS 13 NisHFBTcWJS0sC5BTFwrtfLEE9lEwz2bxHQpWJiu12ZeKpi+7oUSqebX+ 14 --- END LICENSE KEY -----
在学习Linux高并发网络编程开发总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。
posted on 2020-07-07 12:03 Alliswell_WP 阅读(323) 评论(0) 编辑 收藏 举报