天天看點

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 這裡進行修改

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).

select的限制以及poll的使用

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\n", 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\n", 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\n");
                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\n", client[i], --count);
                    close(client[i].fd);
                    client[i].fd = -1;  // 要在這裡移除
                }
                
                //printf("client%d:%s\n", 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\n", 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\n", 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

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

可以看到已經超過了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被關閉。 

繼續閱讀