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
", 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("客户端已经断开连接。。。
");
 80             break;
 81         }
 82         printf("read buf = %s
", buf);
 83         // 小写转大写
 84         for(int i=0; i<len; ++i)
 85         {
 86             buf[i] = toupper(buf[i]);
 87         }
 88         printf("after buf = %s
", 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
");
 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 ......
");
 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
", 
 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("客户端已经断开了连接
");
 91                     close(i);
 92                     // 从读集合中删除
 93                     FD_CLR(i, &reads);
 94                 }
 95                 else
 96                 {
 97                     printf("recv buf: %s
", 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 ......
");
 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("============
");
 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("客户端已经主动断开连接。。。
");
 99                 }
100                 else
101                 {
102                     printf("recv buf = %s
", buf);
103                     for(int k=0; k<len; ++k)
104                     {
105                         buf[k] = toupper(buf[k]);
106                     }
107                     printf("buf toupper: %s
", 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,转载请注明出处。

原文地址:https://www.cnblogs.com/Alliswell-WP/p/CPlusPlus_Linux_12.html