zoukankan      html  css  js  c++  java
  • 转:linux select 多路复用机制

    源地址:http://blog.csdn.net/turkeyzhou/article/details/8609360

     442人阅读 评论(1) 收藏 举报
     
     

    函数作用:

    系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。关于文件句柄,其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr。

    函数原型:

    1. int select(int maxfd,fd_set *rdset,fd_set *wrset,   
    2.            fd_set *exset,struct timeval *timeout);  

    参数说明:

    参数maxfd是需要监视的最大的文件描述符值+1;rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合。struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

    下面的宏提供了处理这三种描述词组的方式:
    FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
    FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
    FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
    FD_ZERO(fd_set *set);用来清除描述词组set的全部位

    参数timeout为结构timeval,用来设置select()的等待时间,其结构定义如下:

    1. struct timeval  
    2. {  
    3.     time_t tv_sec;//second  
    4.     time_t tv_usec;//minisecond  
    5. };  

    如果参数timeout设为:

    NULL,则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件。

    0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。

    特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

    函数返回值:

    执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。错误值可能为:
    EBADF 文件描述词为无效的或该文件已关闭
    EINTR 此调用被信号所中断
    EINVAL 参数n 为负值。
    ENOMEM 核心内存不足

    常见的程序片段如下:

    fs_set readset;
    FD_ZERO(&readset);
    FD_SET(fd,&readset);
    select(fd+1,&readset,NULL,NULL,NULL);
    if(FD_ISSET(fd,readset){……}

    理解select模型:

    理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

    (1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。

    (2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

    (3)若再加入fd=2,fd=1,则set变为0001,0011

    (4)执行select(6,&set,0,0,0)阻塞等待

    (5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

     基于上面的讨论,可以轻松得出select模型的特点:

      (1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务 器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽 然可调,但调整上限受于编译内核时的变量值。本人对调整fd_set的大小不太感兴趣,参考http://www.cppblog.com /CppExplore/archive/2008/03/21/45061.html中的模型2(1)可以有效突破select可监控的文件描述符上 限。

      (2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个 参数。

      (3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

    下面给一个伪码说明基本select模型的服务器模型:

    1. array[slect_len];  
    2.  nSock=0;  
    3.  array[nSock++]=listen_fd;(之前listen port已绑定并listen)  
    4.  maxfd=listen_fd;  
    5.   
    6.  while(1){  
    7.   
    8.   FD_ZERO(&set);  
    9.   
    10.   foreach (fd in array)  
    11.   {  
    12.       fd大于maxfd,则maxfd=fd  
    13.       FD_SET(fd,&set)  
    14.   }  
    15.   
    16.   res=select(maxfd+1,&set,0,0,0);  
    17.   
    18.   if(FD_ISSET(listen_fd,&set))  
    19.   {  
    20.       newfd=accept(listen_fd);  
    21.       array[nsock++]=newfd;  
    22.       if(--res<=0) continue;  
    23.   }  
    24.   
    25.   foreach 下标1开始 (fd in array)  
    26.   {  
    27.       if(FD_ISSET(fd,&tyle="COLOR: #ff0000">set))  
    28.       执行读等相关操作  
    29.       如果错误或者关闭,则要删除该fd,将array中相应位置和最后一个元素互换就好,nsock减一  
    30.       if(--res<=0) continue;  
    31.   }  
    32.   
    33.  }  
    检测键盘有无输入,完整的程序如下:
    1. #include<sys/time.h>  
    2. #include<sys/types.h>  
    3. #include<unistd.h>  
    4. #include<string.h>  
    5. #include<stdlib.h>  
    6. #include<stdio.h>  
    7. int main()  
    8. {  
    9.         char buf[10]="";  
    10.         fd_set rdfds;  
    11.         struct timeval tv;  
    12.         int ret;  
    13.         FD_ZERO(&rdfds);  
    14.         FD_SET(0,&rdfds);   //文件描述符0表示stdin键盘输入  
    15.         tv.tv_sec = 3;  
    16.         tv.tv_usec = 500;  
    17.         ret = select(1,&rdfds,NULL,NULL,&tv);  
    18.         if(ret<0)  
    19.               printf("  selcet");  
    20.         else if(ret == 0)  
    21.               printf("  timeout");  
    22.         else  
    23.               printf("  ret = %d",ret);  
    24.   
    25.         if(FD_ISSET(1,&rdfds))  //如果有输入,从stdin中获取输入字符  
    26.         {  
    27.               printf("  reading");  
    28.               fread(buf,9,1,stdin);  
    29.          }  
    30.          write(1,buf,strlen(buf));  
    31.          printf("  %d  ",strlen(buf));  
    32.          return 0;  
    33. }  
    34. //执行结果ret = 1.  

    利用Select模型,设计的web服务器:

    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <unistd.h>  
    4. #include <errno.h>  
    5. #include <string.h>  
    6. #include <sys/types.h>  
    7. #include <sys/socket.h>  
    8. #include <netinet/in.h>  
    9. #include <arpa/inet.h>  
    10.   
    11. #define MYPORT 88960    // the port users will be connecting to  
    12.   
    13. #define BACKLOG 10     // how many pending connections queue will hold  
    14.   
    15. #define BUF_SIZE 200  
    16.   
    17. int fd_A[BACKLOG];    // accepted connection fd  
    18. int conn_amount;    // current connection amount  
    19.   
    20. void showclient()  
    21. {  
    22.     int i;  
    23.     printf("client amount: %d ", conn_amount);  
    24.     for (i = 0; i < BACKLOG; i++) {  
    25.         printf("[%d]:%d  ", i, fd_A[i]);  
    26.     }  
    27.     printf(" ");  
    28. }  
    29.   
    30. int main(void)  
    31. {  
    32.     int sock_fd, new_fd;  // listen on sock_fd, new connection on new_fd  
    33.     struct sockaddr_in server_addr;    // server address information  
    34.     struct sockaddr_in client_addr; // connector's address information  
    35.     socklen_t sin_size;  
    36.     int yes = 1;  
    37.     char buf[BUF_SIZE];  
    38.     int ret;  
    39.     int i;  
    40.   
    41.     if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {  
    42.         perror("socket");  
    43.         exit(1);  
    44.     }  
    45.   
    46.     if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {  
    47.         perror("setsockopt");  
    48.         exit(1);  
    49.     }  
    50.       
    51.     server_addr.sin_family = AF_INET;         // host byte order  
    52.     server_addr.sin_port = htons(MYPORT);     // short, network byte order  
    53.     server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP  
    54.     memset(server_addr.sin_zero, ''sizeof(server_addr.sin_zero));  
    55.   
    56.     if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {  
    57.         perror("bind");  
    58.         exit(1);  
    59.     }  
    60.   
    61.     if (listen(sock_fd, BACKLOG) == -1) {  
    62.         perror("listen");  
    63.         exit(1);  
    64.     }  
    65.   
    66.     printf("listen port %d ", MYPORT);  
    67.   
    68.     fd_set fdsr;  
    69.     int maxsock;  
    70.     struct timeval tv;  
    71.   
    72.     conn_amount = 0;  
    73.     sin_size = sizeof(client_addr);  
    74.     maxsock = sock_fd;  
    75.     while (1) {  
    76.         // initialize file descriptor set  
    77.         FD_ZERO(&fdsr);  
    78.         FD_SET(sock_fd, &fdsr);  
    79.   
    80.         // timeout setting  
    81.         tv.tv_sec = 30;  
    82.         tv.tv_usec = 0;  
    83.   
    84.         // add active connection to fd set  
    85.         for (i = 0; i < BACKLOG; i++) {  
    86.             if (fd_A[i] != 0) {  
    87.                 FD_SET(fd_A[i], &fdsr);  
    88.             }  
    89.         }  
    90.   
    91.         ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);  
    92.         if (ret < 0) {  
    93.             perror("select");  
    94.             break;  
    95.         } else if (ret == 0) {  
    96.             printf("timeout ");  
    97.             continue;  
    98.         }  
    99.   
    100.         // check every fd in the set  
    101.         for (i = 0; i < conn_amount; i++) {  
    102.             if (FD_ISSET(fd_A[i], &fdsr)) {  
    103.                 ret = recv(fd_A[i], buf, sizeof(buf), 0);  
    104.                   
    105.                 char str[] = "Good,very nice! ";  
    106.                   
    107.                 send(fd_A[i],str,sizeof(str) + 1, 0);  
    108.                   
    109.                   
    110.                 if (ret <= 0) {        // client close  
    111.                     printf("client[%d] close ", i);  
    112.                     close(fd_A[i]);  
    113.                     FD_CLR(fd_A[i], &fdsr);  
    114.                     fd_A[i] = 0;  
    115.                 } else {        // receive data  
    116.                     if (ret < BUF_SIZE)  
    117.                         memset(&buf[ret], '', 1);  
    118.                     printf("client[%d] send:%s ", i, buf);  
    119.                 }  
    120.             }  
    121.         }  
    122.   
    123.         // check whether a new connection comes  
    124.         if (FD_ISSET(sock_fd, &fdsr)) {  
    125.             new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);  
    126.             if (new_fd <= 0) {  
    127.                 perror("accept");  
    128.                 continue;  
    129.             }  
    130.   
    131.             // add to fd queue  
    132.             if (conn_amount < BACKLOG) {  
    133.                 fd_A[conn_amount++] = new_fd;  
    134.                 printf("new connection client[%d] %s:%d ", conn_amount,  
    135.                         inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));  
    136.                 if (new_fd > maxsock)  
    137.                     maxsock = new_fd;  
    138.             }  
    139.             else {  
    140.                 printf("max connections arrive, exit ");  
    141.                 send(new_fd, "bye", 4, 0);  
    142.                 close(new_fd);  
    143.                 break;  
    144.             }  
    145.         }  
    146.         showclient();  
    147.     }  
    148.   
    149.     // close other connections  
    150.     for (i = 0; i < BACKLOG; i++) {  
    151.         if (fd_A[i] != 0) {  
    152.             close(fd_A[i]);  
    153.         }  
    154.     }  
    155.   
    156.     exit(0);  
    157. }  
    补充部分:

    1 基本原理

    Resize icon

    注:select 原理图,摘自 IBM iSeries 信息中心

    1 数据结构与函数原型

    1.1 select

    • 函数原型
         int select(
            int nfds,
            fd_set *readset,
            fd_set *writeset,
            fd_set* exceptset,
            struct timeval *timeout
         );
      
    • 头文件
      • select位于:
        #include <sys/select.h>
        
      • struct timeval位于:
        #include <sys/time.h>
        
    • 返回值

      返回对应位仍然为1的fd的总数。

    • 参数
      • nfds:第一个参数是:最大的文件描述符值+1;
      • readset:可读描述符集合;
      • writeset:可写描述符集合;
      • exceptset:异常描述符;
      • timeout:select 的监听时长,如果这短时间内所监听的 socket 没有事件发生。

    1.2 fd_set

    1.2.1 清空描述符集合

    FD_ZERO(fd_set *)
    

    1.2.2 向描述符集合添加指定描述符

    FD_SET(int, fd_set *)
    

    1.2.3 从描述符集合删除指定描述符

    FD_CLR(int, fd_set *)
    

    1.2.4 检测指定描述符是否在描述符集合中

    FD_ISSET(int, fd_set *)
    

    1.2.5 描述符最大数量

    #define FD_SETSIZE 1024
    

    1.3 描述符集合

    可读描述符集合中可读的描述符,为1,其他为0;可写也类似。异常描述符集合中有异常等待处理的描述符的值为1,其他为0。

    1.4 ioctl

    • 函数原型:

        int ioctl(int handle, int cmd,[int *argdx, int argcx]);
      
    • 头文件:

        #include <sys/ioctl.h>
      
    • 返回值:

      • 0 - 成功
      • 1 - 失败

    2 示例

    程序各部分的解释在注释中。

    #include <sys/socket.h>
    #include <string.h>
    #include <sys/time.h>
    #include <netinet/in.h>
    #include <sys/ioctl.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <stdio.h>
    #include <unistd.h>
    
    #define TRUE  1
    #define FALSE 0
    
    int main(int argc, char *argv[])
    {
        int i, len, rc, on = TRUE;
        int listen_sd, new_sd = 0, max_sd;
        int desc_ready;
        char buffer[80];
        int close_conn, end_server = FALSE;
        struct sockaddr_in server_addr;
        struct timeval timeout;
        struct fd_set master_set, working_set;
    
        // Listen
        listen_sd = socket(AF_INET, SOCK_STREAM, 0);
        if (listen_sd < 0)
        {
            perror("socket() failed");
            exit(-1);
        }
    
        // Set socket options
        rc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
        if (rc < 0)
        {
            perror("setsockopt() failed");
            close(listen_sd);
            exit(-1);
        }
    
        // Set IO control
        rc = ioctl(listen_sd, FIONBIO, (char *) &on);
        if (rc < 0)
        {
            perror("ioctl() failed");
            close(listen_sd);
            exit(-1);
        }
    
        // Bind
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr.sin_port = htons(atoi(argv[1]));
        rc = bind(listen_sd, (struct sockaddr *) &server_addr, sizeof(server_addr));
        if (rc < 0)
        {
            perror("bind() failed
    ");
            close(listen_sd);
            exit(-1);
        }
    
        // Listen
        rc = listen(listen_sd, 32);
        if (rc < 0)
        {
            perror("listen() failed
    ");
            close(listen_sd);
            exit(-1);
        }
    
        // Intialize sd set
        FD_ZERO(&master_set);
        max_sd = listen_sd;
        FD_SET(listen_sd, &master_set);
    
        timeout.tv_sec = 3 * 60;
        timeout.tv_usec = 0;
    
        // Start
        do
        {
            // Copy master_set into working_set
            memcpy(&working_set, &master_set, sizeof(master_set));
    
            printf("Waiting on select()...
    ");
            rc = select(max_sd + 1, &working_set, NULL, NULL, &timeout);
            if (rc < 0)
            {
                perror("  select() failed
    ");
                break;
            }
            if (rc == 0)
            {
                printf("  select() timed out. End program.
    ");
                break;
            }
    
            desc_ready = rc; // number of sds ready in working_set
    
            // Check each sd in working_set
            for (i = 0; i <= max_sd && desc_ready > 0; ++i)
            {
                // Check to see if this sd is ready
                if (FD_ISSET(i, &working_set))
                {
                    --desc_ready;
    
                    // Check to see if this is the listening sd
                    if (i == listen_sd)
                    {
                        printf("  Listeing socket is readable
    ");
                        do
                        {
                            // Accept
                            new_sd = accept(listen_sd, NULL, NULL);
    
                            // Nothing to be accepted
                            if (new_sd < 0)
                            {
                                // All have been accepted
                                if (errno != EWOULDBLOCK)
                                {
                                    perror("  accept() failed
    ");
                                    end_server = TRUE;
                                }
                                break;
                            }
    
                            // Insert new_sd into master_set
                            printf("  New incoming connection - %d
    ", new_sd);
                            FD_SET(new_sd, &master_set);
                            if (new_sd > max_sd)
                            {
                                max_sd = new_sd;
                            }
                        }
                        while (new_sd != -1);
                    }
                    // This is not the listening sd
                    else
                    {
                        close_conn = FALSE;
                        printf("  Descriptor %d is avaliable
    ", i);
                        do
                        {
                            rc = recv(i, buffer, sizeof(buffer), 0);
    
                            // Receive data on sd "i", until failure occurs
                            if (rc < 0)
                            {
                                // Normal failure
                                if (errno != EWOULDBLOCK)
                                {
                                    perror("  recv() failed
    ");
                                    close_conn = TRUE;
                                }
                                break;
                            }
    
                            // The connection has been closed by the client
                            if (rc == 0)
                            {
                                printf("  Connection closed
    ");
                                close_conn = TRUE;
                                break;
                            }
    
                            /* Receiving data succeeded and echo it back
                               the to client */
                            len = rc;
                            printf("  %d bytes received
    ", len);
                            rc = send(i, buffer, len, 0);
                            if (rc < 0)
                            {
                                perror("  send() failed");
                                close_conn = TRUE;
                                break;
                            }
                        }
                        while (TRUE);
    
                        // If unknown failure occured
                        if (close_conn)
                        {
                            // Close the sd and remove it from master_set
                            close(i);
                            FD_CLR(i, &master_set);
    
                            // If this is the max sd
                            if (i == max_sd)
                            {
                                // Find the max sd in master_set now
                                while (FD_ISSET(max_sd, &master_set) == FALSE)
                                {
                                    --max_sd;
                                }
                            } // End of if (i == max_sd)
                        } // End of if (close_conn)
                    }
                }
            }
        }
        while (end_server == FALSE);
    
        /* Close each sd in master_set */
        for (i = 0; i < max_sd; ++i)
        {
            if (FD_ISSET(i, &master_set))
            {
                close(i);
            }
        }
    
        return 0;
    }
  • 相关阅读:
    codeforces C. Fixing Typos 解题报告
    codeforces B. The Fibonacci Segment 解题报告
    codeforces B. Color the Fence 解题报告
    codeforces B. Petya and Staircases 解题报告
    codeforces A. Sereja and Bottles 解题报告
    codeforces B. Levko and Permutation 解题报告
    codeforces B.Fence 解题报告
    tmp
    API 设计 POSIX File API
    分布式跟踪的一个流行标准是OpenTracing API,该标准的一个流行实现是Jaeger项目。
  • 原文地址:https://www.cnblogs.com/xuhj001/p/3360148.html
Copyright © 2011-2022 走看看