zoukankan      html  css  js  c++  java
  • 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,转载请注明出处。

  • 相关阅读:
    微软产品的安全漏洞
    C++中的访问级别
    不是每一滴牛奶都叫特伦苏
    友元关系
    友元关系
    C++中的访问级别
    基类和子类
    基类和子类
    《那些年啊,那些事——一个程序员的奋斗史》——63
    《那些年啊,那些事——一个程序员的奋斗史》——66
  • 原文地址:https://www.cnblogs.com/Alliswell-WP/p/CPlusPlus_Linux_12.html
Copyright © 2011-2022 走看看