select深入理解

作者: XDgbh | 来源:发表于2018-01-12 22:03 被阅读8次

    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

    试想由你实现这样一个集合,可以往里添加任意0~1024之间的数(FD_SET操作),也可以将加入到集合中的数移除——移除一个(FD_CLR操作)或全部清零(FD_ZERO),你会如何实现?
    》》》历史上,系统将描述符集作为整数比特掩码来实现。一种比较好的思路是使用位图bitmap,往集合了添加n时只需将第n个bit位置1,移除n时只需将第n个比特置为0,移除所有数据时,只需将所有bit置为0,可以通过memset操作来实现。fd_set的实现就是采用位图bitmap(关于位图可以参考《编程珠玑》第一章)。

    • fd_set结构体定义
       #define __FD_SETSIZE    1024
       typedef __kernel_fd_set     fd_set;
       typedef struct {
           unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
       } __kernel_fd_set;
    
    • 可以看出,fd_set结构体里面是一个无符号长整型的数组,总共有1024/(8 * 4) = 32个元素,然而这并不是说select最多只能监控32个文件的变化。过去,描述符集被作为一个整数位屏蔽码得到实现,但是这种实现对于多于32个的文件描述符将无法工作。描述符集现在通常用整数数组中的位域表示,数组元素的每一位对应一个文件描述符。例如,一个整数占32位,那么整数数组的第一个元素代表文件描述符0到31,数组的第二个元素代表文件描述符32到63,以此类推,最多可监控32*32 = 1024个文件的变化。

    • 宏FD_SET设置整数数组中对应于fd文件描述符的位为1,宏FD_CLR设置整数数组中对应于fd文件描述符的位为0,宏FD_ZERO设置整数数组中的所有位都为0。fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄。

    //每个ulong为32位,可以表示32个bit。
    //fd  >> 5 即 fd / 32,找到对应的ulong下标i;fd & 31 即fd % 32,找到在ulong[i]内部的位置
     
    #define __FD_SET(fd, fdsetp)   (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] |= (1<<((fd) & 31)))             //设置对应的bit
    #define __FD_CLR(fd, fdsetp)   (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] &= ~(1<<((fd) & 31)))            //清除对应的bit
    #define __FD_ISSET(fd, fdsetp)   ((((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] & (1<<((fd) & 31))) != 0)     //判断对应的bit是否为1
    #define __FD_ZERO(fdsetp)   (memset (fdsetp, 0, sizeof (*(fd_set *)(fdsetp))))                             //memset bitmap
    

    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 深入理解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。据说可调,另有说虽 然可调,但调整上限受于编译内核时的变量值。

    (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判断是否有时间发生)。

    TCP使用select流程图

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

        
      nSock=0;
      array[nSock++]=listen_fd;(之前listen port已绑定并listen)
      maxfd=listen_fd;
    
      while(1){
    
          FD_ZERO(&set);
    
          foreach (fd in array)
          {
              fd大于maxfd,则maxfd=fd
              FD_SET(fd,&set)
          }
    
          res=select(maxfd+1,&set,0,0,0);
    
          if(FD_ISSET(listen_fd,&set))
          {
              newfd=accept(listen_fd);
              array[nsock++]=newfd;
              if(--res<=0) continue;
          }
    
          foreach 下标1开始 (fd in array)
          {
              if(FD_ISSET(fd,&tyle="COLOR: #ff0000">set))
              执行读等相关操作
              如果错误或者关闭,则要删除该fd,将array中相应位置和最后一个元素互换就好,nsock减一
              if(--res<=0) continue;
          }
    
      }
    

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

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #define MYPORT 88960    // the port users will be connecting to
    
    #define BACKLOG 10     // how many pending connections queue will hold
    
    #define BUF_SIZE 200
    
    int fd_A[BACKLOG];    // accepted connection fd
    int conn_amount;    // current connection amount
    
    void showclient()
    {
        int i;
        printf("client amount: %d\n", conn_amount);
        for (i = 0; i < BACKLOG; i++) {
            printf("[%d]:%d  ", i, fd_A[i]);
        }
        printf("\n\n");
    }
    
    int main(void)
    {
        int sock_fd, new_fd;  // listen on sock_fd, new connection on new_fd
        struct sockaddr_in server_addr;    // server address information
        struct sockaddr_in client_addr; // connector's address information
        socklen_t sin_size;
        int yes = 1;
        char buf[BUF_SIZE];
        int ret;
        int i;
    
        if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            perror("socket");
            exit(1);
        }
    
        if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
            perror("setsockopt");
            exit(1);
        }
        
        server_addr.sin_family = AF_INET;         // host byte order
        server_addr.sin_port = htons(MYPORT);     // short, network byte order
        server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
        memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));
    
        if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
            perror("bind");
            exit(1);
        }
    
        if (listen(sock_fd, BACKLOG) == -1) {
            perror("listen");
            exit(1);
        }
    
        printf("listen port %d\n", MYPORT);
    
        fd_set fdsr;
        int maxsock;
        struct timeval tv;
    
        conn_amount = 0;
        sin_size = sizeof(client_addr);
        maxsock = sock_fd;
        while (1) {
            // initialize file descriptor set
            FD_ZERO(&fdsr);
            FD_SET(sock_fd, &fdsr);
    
            // timeout setting
            tv.tv_sec = 30;
            tv.tv_usec = 0;
    
            // add active connection to fd set
            for (i = 0; i < BACKLOG; i++) {
                if (fd_A[i] != 0) {
                    FD_SET(fd_A[i], &fdsr);
                }
            }
    
            ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
            if (ret < 0) {
                perror("select");
                break;
            } else if (ret == 0) {
                printf("timeout\n");
                continue;
            }
    
            // check every fd in the set
            for (i = 0; i < conn_amount; i++) {
                if (FD_ISSET(fd_A[i], &fdsr)) {
                    ret = recv(fd_A[i], buf, sizeof(buf), 0);
                    
                    char str[] = "Good,very nice!\n";
                    
                    send(fd_A[i],str,sizeof(str) + 1, 0);
                    
                    
                    if (ret <= 0) {        // client close
                        printf("client[%d] close\n", i);
                        close(fd_A[i]);
                        FD_CLR(fd_A[i], &fdsr);
                        fd_A[i] = 0;
                    } else {        // receive data
                        if (ret < BUF_SIZE)
                            memset(&buf[ret], '\0', 1);
                        printf("client[%d] send:%s\n", i, buf);
                    }
                }
            }
    
            // check whether a new connection comes
            if (FD_ISSET(sock_fd, &fdsr)) {
                new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);
                if (new_fd <= 0) {
                    perror("accept");
                    continue;
                }
    
                // add to fd queue
                if (conn_amount < BACKLOG) {
                    fd_A[conn_amount++] = new_fd;
                    printf("new connection client[%d] %s:%d\n", conn_amount,
                            inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                    if (new_fd > maxsock)
                        maxsock = new_fd;
                }
                else {
                    printf("max connections arrive, exit\n");
                    send(new_fd, "bye", 4, 0);
                    close(new_fd);
                    break;
                }
            }
            showclient();
        }
    
        // close other connections
        for (i = 0; i < BACKLOG; i++) {
            if (fd_A[i] != 0) {
                close(fd_A[i]);
            }
        }
    
        exit(0);
    }
    
    

    相关文章

      网友评论

        本文标题:select深入理解

        本文链接:https://www.haomeiwen.com/subject/xnrqoxtx.html