• UNP学习笔记(第六章 I/O复用)


    I/O模型

    首先我们将查看UNIX下可用的5种I/O模型的基本区别:

    1.阻塞式I/O

    2.非阻塞式I/O

    3.I/O复用(select和poll)

    4.信号驱动式I/O(SIGIO)

    5.异步I/O(POSIX的aio_系列函数)

    阻塞式I/O模型

    最流行的I/O模型是阻塞式I/O模型,下面以数据报套接字作为例子,有如下的情形

    非阻塞式I/O模型

    进程把一个套接字设置成非阻塞式通知内核:当锁请求的I/O操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误

    前三次调用recvfrom时没有数据可返回,因此内核转而立即返回一个EWOULDBLOCK错误。

    I/O复用模型

    我们可以调用select和poll,阻塞在这两个系统调用中的某一个之上,而不是在真正的I/O系统调用上

    下面小节将详细讲这两个函数的用法

    信号驱动式I/O模型

    我们也可以用信号,让内核在描述符就绪时发送SIGIO信号通知我们。

    在信号发生之后,在信号处理函数中调用recvfrom读取数据报。

    异步I/O模型

    信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。

    select函数

    之前apue的学习笔记也有详细的用法 http://www.cnblogs.com/runnyu/p/4645754.html

    #include <sys/select.h>
    int select(int maxfdp1,fd_set *restrict readfds,fd_set *restrict writefds,fd_set *restrict exceptfds,struct timeval *restrict tvptr);

    参数tvptr指定愿意等待的时间,有下面3种情况

    1.tvptr==NULL 永远等待,直到所指定的描述符中的一个已经准备好或捕捉到一个信号返回。如果捕捉到一个信号,则select返回-1,errno设置为EINTR

    2.tvptr->tv_sec==0 && tvptr->tv_usec==0 不等待,测试所有指定的描述符并立即返回

    3.tvptr->tv_sec!=0 || tvptr->tv_usec!=0  等待指定的秒数和微秒数。当指定的描述符之一已准备好,或当指定的时间值已经超过时立即返回

    中间3个参数readfds、writefds和exceptfds是指向描述符集的指针。这3个描述符集说明了我们关心的可读、可写和处于异常条件的描述符集合。

    对于描述符集fd_set结构,提供了下面4个函数

    #include <sys/select.h>
    int FD_ISSET(int fd,fd_set *fdset);
    void FD_CLR(int fd,fd_set *fdset);
    void FD_SET(int fd,fd_set *fdset);
    void FD_ZERO(fd_set *fdset);

    select第一个参数maxfdp1的意思是“最大文件描述符编号值加1”,在上面3个描述符集中找到最大描述符编号值,然后加1就是第一个参数值。

    select有3个可能的返回值

    1.返回值-1表示出错。如果在所指定的描述符一个都没准备好时捕捉到一个信号,则返回-1

    2.返回0表示没有描述符准备好,指定的时间就过了。

    3.一个正返回值说明了已经准备好的描述符数,在这种情况下,3个描述符集中依旧打开的位对应于已准备好的描述符。

    使用select修订str_cli函数

    新版本改为阻塞于select调用,或是等待标准输入可读,或是等待套接字可读。

    代码如下

     1 #include    "unp.h"
     2 
     3 void
     4 str_cli(FILE *fp, int sockfd)
     5 {
     6     int            maxfdp1;
     7     fd_set        rset;
     8     char        sendline[MAXLINE], recvline[MAXLINE];
     9 
    10     FD_ZERO(&rset);
    11     for ( ; ; ) {
    12         FD_SET(fileno(fp), &rset);
    13         FD_SET(sockfd, &rset);
    14         maxfdp1 = max(fileno(fp), sockfd) + 1;
    15         Select(maxfdp1, &rset, NULL, NULL, NULL);
    16 
    17         if (FD_ISSET(sockfd, &rset)) {    /* socket is readable */
    18             if (Readline(sockfd, recvline, MAXLINE) == 0)
    19                 err_quit("str_cli: server terminated prematurely");
    20             Fputs(recvline, stdout);
    21         }
    22 
    23         if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
    24             if (Fgets(sendline, MAXLINE, fp) == NULL)
    25                 return;        /* all done */
    26             Writen(sockfd, sendline, strlen(sendline));
    27         }
    28     }
    29 }
    View Code

    批量输入

    不幸的是,我们的str_cli函数仍然不正确。考虑下面的情况:

    我们在发送数据给服务器的时候,应该等待应答。这段时间是往返时间加上服务器的处理时间。

    在批量发送数据的时候,当写完最后一个请求后,我们并不能立即关闭连接,因为在客户端跟服务器之间的管道中还有其他的请求和应答

    再看一下我们的str_cli函数:当我们在标准输入键入EOF的时候,str_cli函数就此返回到main函数,随后main函数将终止。

    因此标准输入中的EOF并不意味着我们同时也完成了从套接字的读入:可能仍有请求在去往服务器的路上,或者仍有应答在返回客户的路上。

    我们需要的是一种关闭TCP连接其中一半的方法。这将由下一节shutdown函数来完成。

    shutdown函数

    #inlcude <sys/socket.h>
    int shutdown(int sockfd,int howto)
           //返回:若成功则为0,若出错则为-1

    该函数的行为依赖于howto参数的值

    SHUT_RD   关闭连接的读这一半。套接字中不再有数据可接收,而且套接字接收缓冲区中的现有数据都被丢弃。

    SHUT_WR  关闭连接的写的这一半。当前留在套接字发送缓冲区中的数据将被发送掉,后跟TCP的正常连接终止序列。

    SHUT_RDWR   连接的读半部和写半部都被关闭。

    str_cli函数(再修订版)

    使用shutdown来允许我们正确地处理批量输入。这个版本也废弃了以文本行为中心的代码,从而消除因为缓冲带来的复杂性。

     1 #include    "unp.h"
     2 
     3 void
     4 str_cli(FILE *fp, int sockfd)
     5 {
     6     int            maxfdp1, stdineof;
     7     fd_set        rset;
     8     char        buf[MAXLINE];
     9     int        n;
    10 
    11     stdineof = 0;
    12     FD_ZERO(&rset);
    13     for ( ; ; ) {
    14         if (stdineof == 0)
    15             FD_SET(fileno(fp), &rset);
    16         FD_SET(sockfd, &rset);
    17         maxfdp1 = max(fileno(fp), sockfd) + 1;
    18         Select(maxfdp1, &rset, NULL, NULL, NULL);
    19 
    20         if (FD_ISSET(sockfd, &rset)) {    /* socket is readable */
    21             if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
    22                 if (stdineof == 1)
    23                     return;        /* normal termination */
    24                 else
    25                     err_quit("str_cli: server terminated prematurely");
    26             }
    27 
    28             Write(fileno(stdout), buf, n);
    29         }
    30 
    31         if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
    32             if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
    33                 stdineof = 1;
    34                 Shutdown(sockfd, SHUT_WR);    /* send FIN */
    35                 FD_CLR(fileno(fp), &rset);
    36                 continue;
    37             }
    38 
    39             Writen(sockfd, buf, n);
    40         }
    41     }
    42 }
    View Code

    stdineof是一个初始化为0的新标志:只要该标志位0,每次在主循环中我们总是select标准输入的可读性。

    当我们在标准输入上碰到EOF时,我们把新标志stdineof置为1,调用shutdown以发送FIN,在rset中清除标准输入。

    当我们在套接字上读到EOF时,如果我们已在标准输入上遇到EOF(stdineof==1),那就是正常的终止,于是函数返回。

    TCP回射服务器程序(修订版)

    回顾第五章讲解的TCP回射服务器陈谷,把它重写成使用select来处理任意个客户的单进程程序,而不是为每个客户派生一个子进程。

     1 /* include fig01 */
     2 #include    "unp.h"
     3 
     4 int
     5 main(int argc, char **argv)
     6 {
     7     int                    i, maxi, maxfd, listenfd, connfd, sockfd;
     8     int                    nready, client[FD_SETSIZE];
     9     ssize_t                n;
    10     fd_set                rset, allset;
    11     char                buf[MAXLINE];
    12     socklen_t            clilen;
    13     struct sockaddr_in    cliaddr, servaddr;
    14 
    15     listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    16 
    17     bzero(&servaddr, sizeof(servaddr));
    18     servaddr.sin_family      = AF_INET;
    19     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    20     servaddr.sin_port        = htons(SERV_PORT);
    21 
    22     Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    23 
    24     Listen(listenfd, LISTENQ);
    25 
    26     maxfd = listenfd;            /* initialize */
    27     maxi = -1;                    /* index into client[] array */
    28     for (i = 0; i < FD_SETSIZE; i++)
    29         client[i] = -1;            /* -1 indicates available entry */
    30     FD_ZERO(&allset);
    31     FD_SET(listenfd, &allset);
    32 /* end fig01 */
    33 
    34 /* include fig02 */
    35     for ( ; ; ) {
    36         rset = allset;        /* structure assignment */
    37         nready = Select(maxfd+1, &rset, NULL, NULL, NULL);
    38 
    39         if (FD_ISSET(listenfd, &rset)) {    /* new client connection */
    40             clilen = sizeof(cliaddr);
    41             connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
    42 #ifdef    NOTDEF
    43             printf("new client: %s, port %d
    ",
    44                     Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
    45                     ntohs(cliaddr.sin_port));
    46 #endif
    47 
    48             for (i = 0; i < FD_SETSIZE; i++)
    49                 if (client[i] < 0) {
    50                     client[i] = connfd;    /* save descriptor */
    51                     break;
    52                 }
    53             if (i == FD_SETSIZE)
    54                 err_quit("too many clients");
    55 
    56             FD_SET(connfd, &allset);    /* add new descriptor to set */
    57             if (connfd > maxfd)
    58                 maxfd = connfd;            /* for select */
    59             if (i > maxi)
    60                 maxi = i;                /* max index in client[] array */
    61 
    62             if (--nready <= 0)
    63                 continue;                /* no more readable descriptors */
    64         }
    65 
    66         for (i = 0; i <= maxi; i++) {    /* check all clients for data */
    67             if ( (sockfd = client[i]) < 0)
    68                 continue;
    69             if (FD_ISSET(sockfd, &rset)) {
    70                 if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
    71                         /*4connection closed by client */
    72                     Close(sockfd);
    73                     FD_CLR(sockfd, &allset);
    74                     client[i] = -1;
    75                 } else
    76                     Writen(sockfd, buf, n);
    77 
    78                 if (--nready <= 0)
    79                     break;                /* no more readable descriptors */
    80             }
    81         }
    82     }
    83 }
    84 /* end fig02 */
    View Code

    利用select来等待某个事件发生:新客户连接的建立,数据、FIN或RST的到达。服务器将维护client数组来标识accept返回的套接字描述符。

    pselect函数

    POSIX.1也定义了一个select的变体,称为pselect

    #include <sys/select.h>
    int pselect(int maxfdp1,fd_set *restrict readfds,fd_set *restrict writefds,fd_set *restrict exceptfds,const struct timespec *restrict tsptr,const sigset_t *restrict sigmask);

    除了下列几点外,pselect与select相同

    1.超时值使用的结构不一样,pselect使用的是timespec结构

    2.pselect的超时值被声明为const,保证了调用pselect不会改变此值

    3.pselect可使用可选信号屏蔽字,在调用pselect时,以原子操作的方式安装该信号屏蔽字。在返回时,恢复以前的信号屏蔽字。

    poll函数

    #include <poll.h>
    int poll(struct pollfd fdarray[],nfds_t nfds,int timeout);

    与select不同,poll构造一个pollfd结构的数组,每个数组元素指定一个描述符编号以及我们对描述符感兴趣的条件。

    struct pollfd {
            int   fd;         /* file descriptor */
            short events;     /* requested events */
            short revents;    /* returned events */
    };

    fdarray数组中的元素数由nfds指定

    要测试的条件由events成员指定,函数在相应的revents成员中返回该描述符的状态。

    结构中的events/revents成员应设置为下图所示值的一个或几个

    timeout参数指定poll函数返回前等待多长时间。它是一个指定应等待毫秒值的正值

    TCP回射服务器程序(再修订版)

    我们使用poll代替select重写上面的TCP回射服务器程序。改用poll后,我们只需分配一个pollfd结构的数组来维护客户信息,而不必分配另一个数组。

     1 /* include fig01 */
     2 #include    "unp.h"
     3 #include    <limits.h>        /* for OPEN_MAX */
     4 
     5 int
     6 main(int argc, char **argv)
     7 {
     8     int                    i, maxi, listenfd, connfd, sockfd;
     9     int                    nready;
    10     ssize_t                n;
    11     char                buf[MAXLINE];
    12     socklen_t            clilen;
    13     struct pollfd        client[OPEN_MAX];
    14     struct sockaddr_in    cliaddr, servaddr;
    15 
    16     listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    17 
    18     bzero(&servaddr, sizeof(servaddr));
    19     servaddr.sin_family      = AF_INET;
    20     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    21     servaddr.sin_port        = htons(SERV_PORT);
    22 
    23     Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    24 
    25     Listen(listenfd, LISTENQ);
    26 
    27     client[0].fd = listenfd;
    28     client[0].events = POLLRDNORM;
    29     for (i = 1; i < OPEN_MAX; i++)
    30         client[i].fd = -1;        /* -1 indicates available entry */
    31     maxi = 0;                    /* max index into client[] array */
    32 /* end fig01 */
    33 
    34 /* include fig02 */
    35     for ( ; ; ) {
    36         nready = Poll(client, maxi+1, INFTIM);
    37 
    38         if (client[0].revents & POLLRDNORM) {    /* new client connection */
    39             clilen = sizeof(cliaddr);
    40             connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
    41 #ifdef    NOTDEF
    42             printf("new client: %s
    ", Sock_ntop((SA *) &cliaddr, clilen));
    43 #endif
    44 
    45             for (i = 1; i < OPEN_MAX; i++)
    46                 if (client[i].fd < 0) {
    47                     client[i].fd = connfd;    /* save descriptor */
    48                     break;
    49                 }
    50             if (i == OPEN_MAX)
    51                 err_quit("too many clients");
    52 
    53             client[i].events = POLLRDNORM;
    54             if (i > maxi)
    55                 maxi = i;                /* max index in client[] array */
    56 
    57             if (--nready <= 0)
    58                 continue;                /* no more readable descriptors */
    59         }
    60 
    61         for (i = 1; i <= maxi; i++) {    /* check all clients for data */
    62             if ( (sockfd = client[i].fd) < 0)
    63                 continue;
    64             if (client[i].revents & (POLLRDNORM | POLLERR)) {
    65                 if ( (n = read(sockfd, buf, MAXLINE)) < 0) {
    66                     if (errno == ECONNRESET) {
    67                             /*4connection reset by client */
    68 #ifdef    NOTDEF
    69                         printf("client[%d] aborted connection
    ", i);
    70 #endif
    71                         Close(sockfd);
    72                         client[i].fd = -1;
    73                     } else
    74                         err_sys("read error");
    75                 } else if (n == 0) {
    76                         /*4connection closed by client */
    77 #ifdef    NOTDEF
    78                     printf("client[%d] closed connection
    ", i);
    79 #endif
    80                     Close(sockfd);
    81                     client[i].fd = -1;
    82                 } else
    83                     Writen(sockfd, buf, n);
    84 
    85                 if (--nready <= 0)
    86                     break;                /* no more readable descriptors */
    87             }
    88         }
    89     }
    90 }
    91 /* end fig02 */
    View Code
  • 相关阅读:
    jackson、gson、fastjson
    jackson、gson、fastjson
    Java 常见面试题
    java8的新特性2
    关于mybatis的注释问题
    java PropertyDescriptor 应用及源码分析
    Java 反射总结
    java8的新特性1
    vo、po、dto、bo、pojo、entity、mode如何区分
    倒计时 不加锁 老代码
  • 原文地址:https://www.cnblogs.com/runnyu/p/4660680.html
Copyright © 2020-2023  润新知