zoukankan      html  css  js  c++  java
  • Select单进程非阻塞TCP echo服务器

    Select单进程非阻塞TCP echo服务器

     

    1. select 描述

     

      #include <sys/select.h>

      #include <sys/time.h>

      int select( int maxfdp1,   fd_set *readset,  fd_set *writeset,  fd_set *exceptset,  const struct timeval *timeout);

            返回:正确:就绪描述符数目;超时: 0;出错: -1

      参数说明:

      timeout: 内核等待指定描述符集合中的任意一个就绪的时间。

                         struct timeval {

                              long tv_sec;    //seconds

                              long tv_usec;    //microseconds
                          }; 

                          timeout = NULL:  永远等待下去:仅在有一个描述符准备好I/O才返回。

                          timeout != NULL: 等待timeval中指定的时间返回。

                          timeout != NULL, timeval == 0 : 不等待:检查描述符后立即返回。这称为轮循(polling);

                          前两种情况会被信号中断,并从信号处理程序返回。   

           timeout为const表示函数返回时不会修改timeval,但是有的linux版本会修改timval值。

                          考虑到可移植性,每次都应对timeout进行初始化。  

     

      readset, writeset, exceptset:指定我们让内核测试读写异常条件的描述符:

                         可读:

                         (1) 套接字缓冲区数据字数 >= 套接字接受缓冲区低水位标记的当前大小。 不会阻塞,返回>0的值(准备好读入的数据)。

                               可用SO_RCVLOWAT套接字选项设置标志,TCP和UDP默认1。所以read时可能返回1字节数据,需要反复读。

                         (2) 接受到FIN,读操作不阻塞返回0(EOF)。

                          (3) 监听套接字已完成的连接数不为0,新的连接到达。accept不阻塞。

                           (4) 有一个套接字错误要处理。读不阻塞返回-1。设置errno

     

                         可写:

                         (1) 发送缓冲区可用空间字节数 >= 最低水位。写不阻塞返回>0(例如:传输层接受的字节数)。 TCP和UDP最低水位2048。

                         (2) 连接的写半部关闭,对这样的套接字写产生SIGPIPE信号。

                         (3) 非阻塞的connect套接字已建立连接,或者connect已失败告终。

                         (4) 有一个套接字错误带处理。写不阻塞返回-1.设置errno。

     

                         异常:带外数据到达。

     

      maxfdp1: 指定待测试的描述符个数,为最大描述符 + 1, 从0开始。

     

    2. select 宏和相关说明

     

      select使用描述符集fd_set,通常为一个整数数组,其中每个整数中的每一位对应一个描述符。

           4个宏设置fd_set:

      void FD_ZERO(fd_set *fdset);                      //清空fdset中所有位

           void FD_SET(int fd, fd_set *fdset);                    //fd_set中fd位置位

           void FD_CLR(int fd, fd_set *fdset);                    //fd_set中fd位复位

           void FD_ISSET(int fd, fd_set *fdset);                 //fd_set中fd是否置位

     

      select中的readset,writeset,exceptset = NULL,表示我们不关心这个。select会修改这三个描述符集和,时值——结果参数。

    调用select时指定我们关心的描述符值返回时指示那些已经就位,未就位的置0。因此每次调用select时都需要把我们关心的描述符置1

     

    3. select 缺陷

     

        #include <sys/types.h> 定义了FD_SETSIZE通常为1024。当描述符超出时,可用poll解决。

    通过修改内核的FD_SETSIZE大小,需要重新编译内核,可能带来扩展性问题,不推荐使用。

     

    4. 一个echo服务器程序示例

     

    #include "unp.h"
    
    int main(int argc, char **argv)
    {
        if (argc != 2) {
            err_quit("Usage: a.out <#port>");
        }
    
        int listenfd = Tcp_listen(NULL, argv[1], NULL);
        int flags = Fcntl(listenfd, F_GETFL, 0);
        Fcntl(listenfd, F_SETFL, flags | O_NONBLOCK);
    
        //最大文件描述符
        int maxfd = listenfd;
    
        //连接的客户端描述符
        int client[FD_SETSIZE];
        memset(client, -1, FD_SETSIZE);
    
        //client已使用的描述符最大下标
        int maxi = -1;
    
     //要监听的描述符集合
        fd_set allset;
        FD_ZERO(&allset);
        FD_SET(listenfd, &allset);
    
        for (;;) {
    
            //select会修改描述符集合,每次重新初始化
            fd_set rset = allset;
            DPRINTF("Start to select()");
    
            //select阻塞到可读
            int nready = Select(maxfd + 1, &rset, NULL, NULL, NULL);
    
      //监听描述符可读       
    
            if (FD_ISSET(listenfd, &rset)) {
                DPRINTF("Start to accept a connection");
                //int connfd = Accept(listenfd, NULL, NULL);
                //非阻塞方式忽略若干错误
                int connfd = accept(listenfd, NULL, NULL);
                DPRINTF("connfd %d", connfd);
                if (connfd < 0) {
    #ifdef EPROTO
                    DPRINTF("EPROTO");
                    if (errno == EINTR || errno == EWOULDBLOCK ||
                        errno == ECONNABORTED || errno == EPROTO) {
    #else 
                    if (errno == EINTR || errno == EWOULDBLOCK ||
                        errno == ECONNABORTED) {
    #endif        
                        continue;
                    }  else {
                        err_sys("accept() failed");
                    }
                }
                DPRINTF("Accept a connection");
    
                //设置非阻塞
                int flags = Fcntl(connfd, F_GETFL, 0);
                Fcntl(connfd, F_SETFL, flags | O_NONBLOCK);
    
                int i = 0;
                while (i < FD_SETSIZE) {
                    if (client[i] < 0) {
                        client[i] = connfd;
                        break;
                    }
                    ++i;
                }
                if (i == FD_SETSIZE) {
                    err_quit("Too many client");
                }
                FD_SET(connfd, &allset);
                if (connfd > maxfd) {
                    maxfd = connfd;
                }
                if (i > maxi) {
                    maxi = i;
                }
    
               //检测是否还有准备好的连接
                if (--nready <= 0) {
                    continue;
                }
            } //end if(FD_ISSET)
    
    
           //循环检测已连接的客户端
            DPRINTF("maxi = %d", maxi);
            for (int i = 0; i <= maxi; ++i) {
                int sockfd = client[i];
                if (sockfd < 0) {
                    continue;
                }
                DPRINTF("client[]: i %d, sockfd %d", i, sockfd);
                if (FD_ISSET(sockfd, &rset)) {
                    char line[MAXLINE];
                    int n;
    again:
                    while ((n = read(sockfd, line, MAXLINE)) > 0) {
                        Write(sockfd, line, n);
                    }
                    if (n < 0 && errno == EINTR) {
                        goto again;
                    } else if (n < 0 && errno == EWOULDBLOCK) {
                        ;
                    } else if (n <= 0) {        //0: FIN, close by client; -1: RST, error
                        Close(sockfd);
                        FD_CLR(sockfd, &allset);
                        client[i] = -1;    
                        DPRINTF("read n = %d", n);
                        if (n < 0) {
                            perror("n < 0");
                        }
                    }
                } 
    
                if (--nready <= 0) {
                    break;
                }
            } //end for(0..maxi)
        } //end for(;;)
    
        return 0;
    }

     

     

     

     

     

     

     

     

     

                             

                                

      

          

     

     

     

     

     

     

     

     

     

     

     

     

     

      

     

  • 相关阅读:
    java图书管理系统界面版本+mysql数据库
    java数组实现的超市管理系统(控制台)
    Action<T>和Func<T>委托事例
    简单的委托示例
    使用静态方法CreateInstance()创建数组
    Span复习
    与预定义类型的用户类型强制转换
    实现自定义的索引运算符
    比较运算符的重载
    算术运算符的重载
  • 原文地址:https://www.cnblogs.com/hancm/p/3797782.html
Copyright © 2011-2022 走看看