zoukankan      html  css  js  c++  java
  • UNP学习笔记之四select和poll

    对于I/O操作,有以下几种模型:

    1、阻塞I/O(Blocking I/O):udp协议中的recvfrom在接受数据时进行等待就是使用此模型。

    2.非阻塞I/O:recvfrom不阻塞,在数据未准备好时返回错误。

    3.I/O复用:select和poll,轮询描述符是否准备好,如果准备好了就调用recvfrom获取数据。select可以同时监听多个描述符,但是受限于系统分配的描述符大小。

    4.信号驱动I/O:使用sigaction注册一个信号回调函数,在数据准备好的时候回调该函数。

    5.异步I/O:POSIX中进行了定义,异步I/O是在所有的数据读取操作进行完成后异步通过信号通知给程序。

    5种不同的I/O模型的纵向对比:

    接下来说我们的主角,select和poll,先说select:

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

    Returns: positive count of ready descriptors, 0 on timeout, –1 on error

    第一个参数表示检测的描述符中最大的个数,一般的取值是最大的描述符的值再加上1,max fd plus 1.中间3个参数分别表示需要监听的读,写,错误的描述符集合。为空则表示不感兴趣。如果3个都为空,则可以用来作为sleep函数,精度可以控制到毫秒,因为最后一个参数struct timeval可以表示到毫秒。

    select的返回值>0表示准备好的描述符的个数,但是具体是哪几个描述符准备好,则需要使用FD_ISSET去判断。

    以下4种情况socket准备好读取数据:

    1、socket接受缓冲区的数据大小大于或者等于socket接受缓冲区定义的最小接受长度,可以使用SO_RCVLOWAT选项设置最小接受长度,tcp和udp默认的长度均为1.

    2、连接的一方已经关闭连接。read操作不会阻塞且立即返回0.

    3、socket是一个监听socket时,对应的完成连接数>0,这个socket对应的accept函数不会被阻塞。

    4、socket产生错误时,read操作不会阻塞且返回-1.

    以下4种情况socket准备好写数据:

    1、socket发送缓冲区大小大于或者等于socket发送缓冲区定义的最小发送长度,并且具备以下条件:(1)socket已经连接。(2)socket不需要连接(udp)。如果我们设置socket为非阻塞,write操作不会阻塞且立即返回一个正值。使用SO_SNDLOWAT可以设置最小发送长度。默认大小为2048(udp和tcp)

    2、连接的一方已经关闭。write操作会产生SIGPIPE.

    3、使用非阻塞的connect函数已经完成或者connect函数失败。

    4、socket产生错误。

    close有两个限制可由函数shutdown来避免:
    close将描述字的访问计数减1,仅在此计数为0时才关闭套接口
    shutdown可激发TCP的正常连接终止序列, 而不管访问计数。
    close终止了数据传送的两个方向:读和写。
    shutdown终止的数据传送的两个方向:读和写, 或其中任一方向:读或写

    定义:
    int shutdown( int sockfd, int howto) ;
    howto选项:
    SHUT_RD 关闭连接的读一半
    SHUT_WR 关闭连接的写这一半
    SHUT_RDWR 关闭连接读读和写

    我们来看看一个进程使用select来处理并发的情况:

    #include    "unp.h"
    
    int main(int argc, char **argv)
    {
        int                    i, maxi, maxfd, listenfd, connfd, sockfd;
        int                    nready, client[FD_SETSIZE];
        ssize_t                n;
        fd_set                rset, allset;
        char                buf[MAXLINE];
        socklen_t            clilen;
        struct sockaddr_in    cliaddr, servaddr;
    
        listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family      = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port        = htons(SERV_PORT);
    
        Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    
        Listen(listenfd, LISTENQ);
    
        maxfd = listenfd;            //首先把监听fd加入到select中
        maxi = -1;                    //表示最大的可用接入fd的下标
        for (i = 0; i < FD_SETSIZE; i++)
            client[i] = -1;            //接入fd的数组集合,-1表示未使用
        FD_ZERO(&allset);
        FD_SET(listenfd, &allset);  //加入监听fd到select中
        
    
        for ( ; ; ) {
            rset = allset;            
            nready = Select(maxfd+1, &rset, NULL, NULL, NULL);//注册select,只注册了可读集合,并且非阻塞
    
            if (FD_ISSET(listenfd, &rset)) {    //如果监听fd准备好,则新分配一个接入fd
                clilen = sizeof(cliaddr);
                connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
    #ifdef    NOTDEF
                printf("new client: %s, port %d\n",
                    Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
                    ntohs(cliaddr.sin_port));
    #endif
    
                for (i = 0; i < FD_SETSIZE; i++)
                    if (client[i] < 0) {
                        client[i] = connfd;    //加入到接入fd数组中
                        break;
                    }
                    if (i == FD_SETSIZE)//如果接入fd数组不存在-1的index,则表示已经接入过多的连接
                        err_quit("too many clients");
    
                    FD_SET(connfd, &allset);    //把最新的接入fd加入select中
                    if (connfd > maxfd)
                        maxfd = connfd;            //更新max fd
                    if (i > maxi)
                        maxi = i;                //更新最大的可用index
    
                    if (--nready <= 0)
                        continue;                //如果nready只是等于1,则表明只有一个链接。
            }
    
            for (i = 0; i <= maxi; i++) {    //查询所有可用的接入fd,处理数据
                if ( (sockfd = client[i]) < 0)
                    continue;
                if (FD_ISSET(sockfd, &rset)) {
                    if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {                  
                        Close(sockfd);
                        FD_CLR(sockfd, &allset);
                        client[i] = -1;
                    } else
                        Writen(sockfd, buf, n);
    
                    if (--nready <= 0)
                        break;                //如果nready只是等于1,则表明只有一个链接。
                }
            }
        }
    }

    不过这样的程序会存在Dos攻击,链接阻塞在read,防止dos攻击的方法是使用非阻塞io,或者使用另外的进程和线程处理逻辑,设置超时等。

    接下来我们看看poll,定义如下:

    #include <poll.h>

    int poll (struct pollfd *fdarray, unsigned long nfds, int timeout);

    poll函数的第一个参数是指向名为pollfd的结构体指针。pollfd结构体定义如下:

    struct pollfd {
      int     fd;       /* descriptor to check */
      short   events;   /* events of interest on fd */
      short   revents;  /* events that occurred on fd */
    };

    pollfd里面,events表示等待的事件,revents表示实际发生的事件。根据结果可以做不同的处理。下表是poll的事件定义:

    poll的第二个参数表示fdarray的个数,第三个参数是超时时间,单位是毫秒。

    poll的例子与select基本类似,这里就不多说,具体参见unp。

    最后说poll比select优秀的地方:

    (1)不再有fd个数的上限限制,可以将参数ufds想象成栈低指针,nfds是栈中元素个数,该栈可以无限制增长
    (2)引入pollfd结构,将fd信息、需要监控的事件、返回的事件分开保存,则poll返回后不会丢失fd信息和需要监控的事件信息,也就省略了select模型中前面的循环操作,返回后的循环仍然不可避免。另每次poll阻塞操作都会自动把上次的revents清空。

    以下是在学习过程中找到的一些好的文章和讨论:

    http://bbs3.chinaunix.net/thread-1283223-1-1.html

    http://linux.chinaunix.net/techdoc/develop/2008/09/27/1034861.shtml

    http://blog.csdn.net/pmunix/archive/2008/01/13/2041512.aspx

    http://www.cs.uwaterloo.ca/~brecht/theses/Ostrowski-MMath.pdf

  • 相关阅读:
    Oracle数据库安装
    [转]卡西欧手表调日期正确方法
    python密码处理(可用于生产模式)
    [转]python对json的相关操作
    [转]Python中的with…as…
    Python标准库--os模块
    我的github代码添加
    Python正则表达式+自创口诀
    自己总结python用xlrdxlwt读写excel
    CentOS安装+配置+远程
  • 原文地址:https://www.cnblogs.com/shanks/p/1520902.html
Copyright © 2011-2022 走看看