zoukankan      html  css  js  c++  java
  • select的限制以及poll的使用

    1.先说select在多路IO中的限制:
    1)linux中每个程序能够打开的最多文件描述符是有限制的。默认是1024.
    可以通过ulimit -n进行查看和修改:

    xcy@xcy-virtual-machine:~/test/sock10_poll$ ulimit -n
    1024
    xcy@xcy-virtual-machine:~/test/sock10_poll$ ulimit -n 2048  // n 这里进行修改
    xcy@xcy-virtual-machine:~/test/sock10_poll$ ulimit -n
    2048
    xcy@xcy-virtual-machine:~/test/sock10_poll$

    这就意味着我们的服务器进程最多能打开1024个文件描述符。(而且0 1 2 还已经被占用了)。
    而且一般服务器还有一个监听套接字,所以当第1021个连接发起时就会失败(假定前面没有关闭)。
    2)我们知道select的第2-4个参数是这个类型的fd_set。这里东西可以把它看成是数组。这个数组也是有边界的。
    边界就是 FD_SETSIZE。
    man select的部分截取:

    NOTES
           An fd_set is a fixed size buffer.  Executing FD_CLR() or FD_SET()  with
           a value of fd that is negative or is equal to or larger than FD_SETSIZE
           will result in undefined behavior.  Moreover, POSIX requires fd to be a
           valid file descriptor.

    这个数组最大就是FD_SETSIZE。超过这个数以后就会越界。
    FD_SETSIZE定义在系统的头文件中(具体哪个文件我没找到),可以修改那个头文件,再重新编译内核。这样比较麻烦。
    想要突破这个限制,就需要poll函数了。


    2.poll函数
    先看man手册(截取部分):

    SYNOPSIS
           #include <poll.h>
           int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    DESCRIPTION
           poll()  performs a similar task to select(2): it waits for one of a set
           of file descriptors to become ready to perform I/O.

    也可以用来监测多个IO。但是不会被FD_SETSIZE限制。
    参数:
    fds:一般是一个struct pollfd类型的数组,
    nfds:要监视的描述符的数目。
    timeout:超时时间,-1表示不会超时。0表示立即返回,不阻塞进程。 >0表示等待数目的毫秒数。
    返回值:
    -1:出错了,错误代码在errno中
    0:设置了超时时间,这里表示超时了
    >0:数组中fds准备好读、写、或异常的那些描述符的总数量
    下面来看看struct pollfd这个结构体:

           struct pollfd {
                   int   fd;         /* file descriptor */
                   short events;     /* requested events  请求的事件,具体哪些值见下面 */
                   short revents;    /* returned events  返回的事件,有点像传出参数。哪个事件发生了就存储在这里*/
               };
           //  events和revents的值可以是下面:
           The  bits that may be set/returned in events and revents are defined in
           <poll.h>:
                  POLLIN There is data to read.  //可读
                  POLLPRI  
                         There is urgent data to read (e.g., out-of-band  data  on
                         TCP socket; pseudoterminal master in packet mode has seen
                         state change in slave).
                  POLLOUT  // 可写
                         Writing now will not block.
                  POLLRDHUP (since Linux 2.6.17)
                         Stream socket peer closed connection, or shut down  writ‐
                         ing  half  of  connection.   The _GNU_SOURCE feature test
                         macro must be defined (before including any header files)
                         in order to obtain this definition.
                  POLLERR  // 出错
                         Error condition (output only).
                  POLLHUP
                         Hang up (output only).
                  POLLNVAL
                         Invalid request: fd not open (output only).

    3.实例:
    先看server端:

    #include<sys/types.h>
    #include<sys/socket.h>
    #include<sys/select.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<poll.h>
    #include<stdlib.h>
    #include<stdio.h>
    #include<string.h>
    #include<errno.h>
    
    //#define CLIENTCOUNT FD_SETSIZE
    #define CLIENTCOUNT 2048
    
    int main(int argc, char **argv)
    {
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if(listenfd < 0)
        {
            perror("socket");
            return -1;
        }
        
        unsigned short sport = 8080;
        if(argc == 2)
        {
            sport = atoi(argv[1]);
        }
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        printf("port = %d
    ", sport);
        addr.sin_port = htons(sport);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        if(bind(listenfd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
        {
            perror("bind");
            return -2;
        }
    
        if(listen(listenfd, 20) < 0)
        {
            perror("listen");
            return -3;
        }
        struct sockaddr_in connaddr;
        int len = sizeof(connaddr);
        
        int i = 0, ret = 0;
        struct pollfd client[CLIENTCOUNT];
        for(i = 0; i<CLIENTCOUNT; i++)
            client[i].fd = -1;
    
        int maxi = 0;
        client[0].fd = listenfd;
        client[0].events = POLLIN;
    
        int count = 0;
        int nready = 0;
        char buf[1024] = {0};
        while(1)
        {
            nready = poll(client, maxi+1, -1);
            if(nready == -1)
            {
                perror("select");
                            return -3;
    
            }
            if(nready == 0)
            {
                continue;
            }
    
            if(client[0].revents & POLLIN)
            {
                int conn = accept(listenfd, (struct sockaddr*)&connaddr, &len);
                if(conn < 0)
                {
                    perror("accept");
                    return -4;
                }
            
                char strip[64] = {0};
                char *ip = inet_ntoa(connaddr.sin_addr);
                strcpy(strip, ip);
                printf("client connect, conn:%d,ip:%s, port:%d, count:%d
    ", conn, strip,ntohs(connaddr.sin_port), ++count);
    
                int i = 0;
                for(i = 0; i<CLIENTCOUNT; i++)
                {
                    if(client[i].fd == -1)
                    {
                        client[i].fd = conn;
                        client[i].events = POLLIN;
                        if(i > maxi)
                            maxi = i;
                        break;
                    }
                }
                if(i == CLIENTCOUNT)
                {
                    printf("to many client connect
    ");
                    exit(0);
                }      
                if(--nready <= 0)
                    continue;
            }
            
            for(i = 0; i < CLIENTCOUNT; i++)
            {
                if(client[i].fd == -1)
                    continue;
                if(client[i].revents & POLLIN)
                {
                    ret = read(client[i].fd, buf, sizeof(buf));
                    if(ret == -1)
                    {
                        perror("read");
                        return -4;
                    }
                    else if(ret == 0)
                    {
                        printf("client close remove:%d, count:%d
    ", client[i], --count);
                        close(client[i].fd);
                        client[i].fd = -1;  // 要在这里移除
                    }
                    
                    //printf("client%d:%s
    ", client[i], buf);
                    write(client[i], buf, sizeof(buf));
                    memset(buf, 0, sizeof(buf));
                    if(--nready <= 0)
                        continue;
                }
            }        
        }
    
        close(listenfd);
        return 0;
    }

    所有的client都存放在数组struct pollfd client[CLIENTCOUNT]中。每连接一个就加入到数组中。

    关于这个server 的理解,可以参考这个的例子(这两个例子其实很像):http://www.cnblogs.com/xcywt/p/8087677.html  

    下面是client端:

    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<sys/select.h>
    #include<stdlib.h>
    #include<stdio.h>
    #include<string.h>
    
    /*
    这里是暴力测试最多能连接几个。由于进程能打开的fd的限制最多的1024.
    所以这里最多是1024 - 3. 也就是连接1022个的时候就出错了
    (0  1  2 已经被占用了)
    
    设置成2048就是另外一个结果了
    */
    int main(int argc, char **argv)
    {
        unsigned short sport = 8080;
        if(argc == 2)
        {
            sport = atoi(argv[1]);
        }
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        printf("port = %d
    ", sport);
        addr.sin_port = htons(sport);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        int count = 0;
        while(1)
        {
            int sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if(sockfd < 0)
            {
                perror("socket");
                sleep(5); // 这个是为了保证连接完成
                return -1;
            }
            if(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
            {
                perror("connect");
                return -2;
            }
    
            struct sockaddr_in addr2;
            socklen_t len = sizeof(addr2);
            if(getpeername(sockfd, (struct sockaddr*)&addr2, &len) < 0)
            {
                perror("getsockname");
                return -3;
            }
    
            printf("Server: port:%d, ip:%s, count:%d
    ", ntohs(addr2.sin_port), inet_ntoa(addr2.sin_addr), ++count);
        }
        return 0;
    }

    client就是暴力连接,测试能连接的最大的数目:运行:
    注意运行的终端需要将能打开的最大描述符设成2048,如果不改的话看不出效果。
    结果(截取部分):
    server:(最多只能有2048 - 4个能连接上来,0 1 2 已经被占用,还有一个监听套接字)

    ......
    client connect, conn:2040,ip:127.0.0.1, port:38220, count:2037
    client connect, conn:2041,ip:127.0.0.1, port:38222, count:2038
    client connect, conn:2042,ip:127.0.0.1, port:38224, count:2039
    client connect, conn:2043,ip:127.0.0.1, port:38226, count:2040
    client connect, conn:2044,ip:127.0.0.1, port:38228, count:2041
    client connect, conn:2045,ip:127.0.0.1, port:38230, count:2042
    client connect, conn:2046,ip:127.0.0.1, port:38232, count:2043
    client connect, conn:2047,ip:127.0.0.1, port:38234, count:2044
    accept: Too many open files
    xcy@xcy-virtual-machine:~/test/sock10_poll$

    client的(截取):

    ......
    Server: port:8080, ip:127.0.0.1, count:2036
    Server: port:8080, ip:127.0.0.1, count:2037
    Server: port:8080, ip:127.0.0.1, count:2038
    Server: port:8080, ip:127.0.0.1, count:2039
    Server: port:8080, ip:127.0.0.1, count:2040
    Server: port:8080, ip:127.0.0.1, count:2041
    Server: port:8080, ip:127.0.0.1, count:2042
    Server: port:8080, ip:127.0.0.1, count:2043
    Server: port:8080, ip:127.0.0.1, count:2044
    Server: port:8080, ip:127.0.0.1, count:2045
    socket: Too many open files
    xcy@xcy-virtual-machine:~/test/sock10_poll$

    可以看到已经超过了1024个了。

    poll可以突破FD_SETSIZE的限制,但是还是无法突破进程能打开最大文件描述符的限制。

    下面命令可以查看进程能打开的最大文件描述符限制(ulimit不能设置无限大),和计算机的内存有关:

    cat /proc/sys/fs/file-max

    5.关于上面client的sleep(5)的作用:

    如果没有sleep(5):那么client这边连接第2045的时候,进程会立即退出。就会关闭进程打开的套接字。TCP协议就会给server发送FIN段。从而server这边就会检测到有的client已经关闭了。所以server这边的count就可能会不准确了。因为有的已经关闭了,就可以再次打开。

    如果加上sleep(5):就可以保证前面2044个连接都发送过去了,只是第2045个连接会失败。但是server也只能接收2044个连接。保证在关闭之前没有client的fd被关闭。 

  • 相关阅读:
    网络七层协议之部分协议详解
    C/C++书籍分享(百度网盘版)
    poi导出excel实例
    java map去除空值和null,等一些好用的工具类
    mysql查询出来的sum结果后边有.0如何去除
    java form 表单提交多条数据到后台!
    使用jsp链接jdbc数据库并将数据显示出来
    对接短信平台wsdl获取代码方式!并使用
    Flutter! 记录一下艰难的Flutter+vscode+真机,第一次调试成功
    微信公众号开发-微信公众号网页H5静默授权!!!
  • 原文地址:https://www.cnblogs.com/xcywt/p/8120166.html
Copyright © 2011-2022 走看看