zoukankan      html  css  js  c++  java
  • UNP第六章 I/O复用

    一 I/O模型

    1.1 阻塞式I/O模型

    1.2 非阻塞式I/O模型

    1.3 I/O复用模型

     

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

    参数说明:

    1.timeout:

    struct timeval{
      long tv_sec;   //seconds
      long tv_usec;  //microseconds    
    };

    (1)设为NULL时,永远等待;
    (2)等待一段固定时间;
    (3)设为(struct timeval)0,不等待,检查描述符后立即返回。
    Ps:const 限定词表示不会被在函数返回时被修改

    2.readset,writeset和exceptset指定要让内核测试读、写和异常条件的描述符使用描述符集:通常是一个整数数组,其中每个整数中的每一位对应一个描述符,假设使用32位整数,那么该数组第一个元素对应于描述符0-31

    实现细节隐藏在fd_set数据结构和四个宏中:

    void FD_ZERO(fd_set *fdset);    //clear all bits in fdset
    void FD_SET(int fd, fd_set *fdset);  //turn on the bit for fd in fdset
    void FD_CLR(int fd, fd_set *fdset);  //turn off the bit for fd in fdset
    int FD_ISSET(int fd, fd_set *fdset); //is the bit for fd on in fdset?

    Ps:如果对某一个条件不感兴趣,可把它设为NULL

     3. maxfdp1 : 指定待测试的描述符个数,它的值是待测试的最大描述符加1,描述符1,2,3...maxfdp-1均被测试,头文件<sys/select.h>中定义的FD_SETSIZE常值是fd_set中的描述符总数,其值通常是1024。

     4. 返回值说明:

     该函数返回值指示哪些描述符已就绪,可通过FD_ISSET宏来测试,描述符集内任何未与就绪描述符对应的位返回时均清成0。所以,每次重新调用select函数,需要把所有描述符集内关心的位置1

     该函数返回值表示跨所有描述符集的已就绪的总位数,如果在任何描述符就绪之前定时器到时,则返回0,返回-1表示出错。

     5.描述符就绪条件:

    接收低水位标记和发送低水位标记的目的:
    允许应用进程控制在select返回可读或可写条件之前有多少数据可读或有多大空间可写。

    6.例子:

    void str_cli(FILE *fp, int sockfd)
    {
        int maxfdp1;
        fd_set rset;
        char sendline[MAXLINE],recvline[MAXLINE];
    
        FD_ZERO(&rset);
        for(;;)
        {
            FD_SET(fileno(fp),&rset); //fileno函数把标准I/O文件指针转换为对应的描述符
            FD_SET(sockfd,&rset);
            maxfdp1 = max(fileno(fp),sockfd) + 1;
            Select(maxfdp1, &rest, NULL, NULL, NULL);
    
            if(FD_ISSET(sockfd,&rset))
            {
                if(Readline(sockfd,recvline,MAXLINE) == 0)
                    err_quit("str_cli:server terminated prematurely");
                Fputs(recvline,stdout);
            }
    
            if(FD_ISSET(fileno(fp),&rset))
            {
                if(Fgets(sendline,MAXLINE,fp) == NULL)
                {
                    return;
                }
                Writen(sockfd,sendline,strlen(sendline));
            }
        }
    }

    三 针对二中例子存在两个问题并进行解决

    在批量方式下,客户能够以网络可以接受的最快速度持续发送请求,服务器以相同速度处理它们并发回应答,导致时刻7时管道充满。
    假设发出第一个请求后立即发出下一个,客户能够以网络可以接受的最快速度持续发送请求,并且能够以网络可提供的最快速度处理应答。

    则存在两个问题:
    问题一:在批量方式下,标准输入的EOF并不代表我们同时完成socket的读入,可能仍有请求在发给服务器,或仍有应答在返回给客户,我们希望给服务器发送一个FIN,告知已经完成数据发送,但仍保持socket描述符打开以便读取。

    问题二:在上述例子中,用fgets读取输入,这使得已可用的文本输入行被读入到stdio所用的缓冲区,但fgets只返回其中第一行。所以只消费了第一行,stdio缓冲区仍有遗留。同样道理适用于readline函数。

    So, how to deal with it?

    采用shutdown函数!!

    shutdown函数:

    (1)close将描述符的引用计数减1,仅在该计数变为0时才关闭socket,使用shutdown可以不管引用计数就激发TCP的正常连接终止序列
    (2)告知对端我们已完成数据传送。

    #include<sys/socket.h>
    int shutdown(int sockfd, int howto);

    howto有三个参数:

    SHUT_RD:   socket中不再有数据可接受,且接受缓冲区中现有数据被丢弃;

    SHUT_WR:  半关闭:当前留在socket发送缓冲区中的数据将被发送,后跟TCP正常连接终止序列,进程不可以对该socket调用任何写函数。

    SHUT_RDWR:  先调用SHUT_RD,再调用SHUT_WR。

    void str_cli(FILE *fp, int sockfd)
    {
        int maxfdp1,stdineof;
        fd_set rset;
        char buf[MAXLINE];
        int n;
    
        stdineof = 0;
        FD_ZERO(&rset);
        for(;;)
        {
            if(stdineof == 0) FD_SET(fileno(fp),&rset); //fileno函数把标准I/O文件指针转换为对应的描述符
            FD_SET(sockfd,&rset);
            maxfdp1 = max(fileno(fp),sockfd) + 1;
            Select(maxfdp1, &rest, NULL, NULL, NULL);
    
            if(FD_ISSET(sockfd,&rset))
            {
                if((n = Read(sockfd,buf,MAXLINE)) == 0)   //改用Read,解决了readline的问题
                {
                    if(stdineof == 1)  return;   //这是连接已正常终止,就不读了,读结束
                     else err_quit("str_cli:server terminated prematurely");
                }
                Write(fileno(stdout),buf,n);
            }
    
            if(FD_ISSET(fileno(fp),&rset))
            {
                if((n = Read(fileno(fp),buf,MAXLINE))== 0)   //读到EOF
                {
                    stdineof = 1;      //置位标志位
                    Shutdown(sockfd,SHUT_WR);    //发送FIN,正常终止,但注意此时只是写关闭,仍可以通过socket读
                    FD_CLR(fileno(fp),&rset);
                    continue;
                }
                Writen(sockfd,buf,n);
            }
        }
    }

    四 多客户单服务器程序

    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(sercaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htos(SERV_PORT);
    
        Bind(listenfd,(SA*)&servaddr, sizeof(servaddr));
    
        Listen(listenfd,LISTENQ);
    
        maxfd = listenfd; 
        maxi = -1;
        for(i=0;i<FD_SETSIZE;i++)
        {
            client[i] = -1;
        }
        FD_ZERO(&allset);
        FD_SET(listenfd,&allset);
    
        for(;;)
        {
            rest = allset;
            nready = Select(maxfd+1, &rset, NULL, NULL, NULL);
    
            if(FD_ISSET(listenfd,&rset))    //当有新的客户连接时
           {
               clilen = sizeof(cliaddr);
               connfd = Accept(listenfd,(SA*)&cliaddr,&client);
    
               for(i=0;i<FD_SETSIZE;i++)
               {
                   if(client[i]<0)       //找到第一个为-1的位置,放入已连接描述符索引
                   {
                       client[i] = connfd;
                       break;
                   }
               }
    
               if(i == FD_SETSIZE) err_quit("too many clients");
    
               FD_SET(connfd,&allset);   //将新的描述符添加到allset中
               if(connfd > maxfd)
                      maxfd = connfd;   //for select
               if(i > maxi)
                      maxi = i;      //max index in client[] array
               if(--nready <= 0)
                   continue;   //没有可读的描述符了
            }
    
            for(i=0;i<=maxi;i++)
            {
                if((sockfd = client[i]) < 0) continue;
                if(FD_ISSET(sockfd,&rest))
                {
                    if((n = Read(sockfd, buf, MAXLINE)) == 0)   //该sockfd对应的客户想要关闭连接
                    {
                         Close(sockfd);
                         FD_CLR(sockfd,&allset);
                         client[i] = -1;
                    }else 
                      Writen(sockfd,buf,n);
    
                     if(--nready <= 0)
                         breal;   //没有可读的描述符了
                }
            }
        }
    }

    五 poll函数

    #include <poll.h>
    
    int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
    
    struct pollfd{
        int fd;   //descriptor to check
        short events;  //events of interest on fd
        short revents; //events that occurred on fd 
    };

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

    对之前例子的修改:

    int main(int argc, char **argv)
    {
        int i,maxi,listenfd,connfd,sockfd;
        int nready;
        ssize_t n;
        char buf[MAXLINE];
        socklen_t clilen;
        struct pollfd client[OPEN_MAX];
        struct sockaddr_in cliaddr,servaddr;
    
        listenfd = Socket(AF_INET,SOCK_STREAM,0);
    
        bzero(&servaddr,sizeof(sercaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htos(SERV_PORT);
    
        Bind(listenfd,(SA*)&servaddr, sizeof(servaddr));
    
        Listen(listenfd,LISTENQ);
    
        client[0].fd = listenfd;
        client[0].events = POLLRDNORM;
        for(i=1;i<OPEN_MAX;i++)
            client[i].fd = -1;   //初始化,代表无效
        maxi = 0;     //max index into client[] array
    
        for(;;)
        {
            nready = Poll(client,maxi+1,INFTIM);
    
            if(client[0].revents & POLLRDNORM)    //当有新的客户连接时
           {
               clilen = sizeof(cliaddr);
               connfd = Accept(listenfd,(SA*)&cliaddr,&client);
    
               for(i=1;i<OPEN_MAX;i++)
               {
                   if(client[i]<0)       //找到第一个为-1的位置,放入已连接描述符索引
                   {
                       client[i].fd = connfd;
                       break;
                   }
               }
    
               if(i == OPEN_MAX) err_quit("too many clients");
    
               client[i].events = POLLRDNORM;  
              
               if(i > maxi)
                      maxi = i;      //max index in client[] array
               if(--nready <= 0)
                   continue;   //没有可读的描述符了
            }
    
            for(i=1;i<=maxi;i++)
            {
                if((sockfd = client[i].fd) < 0) continue;
                if(client[i].revents & (POLLRDNORM | POLLERR))
                {
                    if((n = read(sockfd, buf, MAXLINE)) < 0)   
                    {
                         if(errno == ECONNRESET)
                         {
                             Close(sockfd);
                             client[i].fd = -1;
                         }
                         else err_sys("read error");
                    }else if(n == 0)  //该sockfd对应的客户想要关闭连接
                    {
                        Close(sockfd);
                        client[i].fd = -1;
                    } else
                      Writen(sockfd,buf,n);
    
                     if(--nready <= 0)
                         breal;   //没有可读的描述符了
                }
            }
        }
    }
  • 相关阅读:
    SQL语句实例学习汇总
    sql语句一些实用技巧for oracle
    无限级递归生成HTML示例及ListBox,DropDownList等无限树
    另类Sql语句直接导出表数据到Execl
    powerdesigner中sql脚本小写转大写,去双引号
    C#中利用jQuery获取Json值示例,Ajax方式。
    利用jquery解决下拉菜单被Div遮挡问题
    获取Textarea 元素当前的光标位置及document.selection.createRange()资料
    oracle中一些常用函数
    IE6 动态创建 iframe 无法显示的 bug,万恶的IE6
  • 原文地址:https://www.cnblogs.com/dzy521/p/9394746.html
Copyright © 2011-2022 走看看