《UNIX网络编程卷1》读书笔记--第六章I/O复用:select和poll函数

前言

I/O复用采用轮询的方式处理多个描述符,当有文件准备好时,就通知进程。

关注点

  • I/O复用的应用场合
  • 采用I/O复用的客户端和服务器程序

I/O复用的应用场合

1. 当客户处理多个描述符时(通常是交互式输入和网络套接字),必须使用I/O复用,才能即使告知用户程序套接字的情况
2. 如果一个TCP服务器既要处理监听又要处理连接套接字,一般要用I/O复用
3. 如果既要处理TCP,又要处理UDP,一般要用I/O复用
4. 如果一个服务器要处理多个服务或多个协议如inet守护进程,一般要用I/O复用

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);
    void FD_ZERO(fd_set *fdset);
    void FD_SET(int fd, fd_set *fdset);
    void FD_CLR(int fd, fd_set *fdset);
    int FD_ISSET(int fd, fd_set *fdset);
//返回就绪描述符数目,若超时则为0,出错为1
批量输入问题:
    close终止了读和写两个方向的数据传送
    shutdown可以通过SHUT_RD等参数实现读半关闭从而使得还在网络中的数据传输完整
    兑现了TCP的可靠传输的性质。
select和缓冲区的关系:
    select只认是否有描述符准备好,也就是观察内核,但是stdio和sendline(用户级)有自己的缓冲区,并且读写有可能导致自己缓冲区中留有数据,select没有看到,因此还是阻塞在等待内核描述符准备好,导致stdio和sendline中的数据没有完全处理掉(结合程序看比较清楚,也可以看[这里](http://www.cppblog.com/mysileng/archive/2013/01/15/197284.html))。
    struct timeval{
        long tv_sec;
        long tv_usec;
    }
timeval结构中的微妙级数分辨率一般不可能达到,一般都是向上舍入10ms的倍数,另外还涉及调度的延迟。
  • 采用I/O复用的客户端和服务器程序
  • 使用selecet的客户端程序

    版本一:

void str_cli(FILE * fp, int sockfd){

  char sendline[MAXLINE], recvline[MAXLINE];
  int maxfdp1;
  fd_set rset;

  FD_ZERO(&rset);
  for( ; ; ){
    FD_SET(fileno(fp), &rset);
    FD_SET(sockfd, &rset);
    maxfdp1 = max(fileno(fp), sockfd) + 1;
    Select(maxfdp1, &rset, 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));
    }
  }
}

版本二:

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);
    }
    FD_SET(sockfd, &rset);
    maxfdp1 = max(fileno(fp), sockfd) + 1;
    Select(maxfdp1, &rset, NULL, NULL, NULL);
    if(FD_ISSET(sockfd, &rset)){
      if((n = Read(sockfd, buf, MAXLINE)) == 0){
        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){
        stdineof = 1;
        Shutdown(sockfd, SHUT_WR);
        FD_CLR(fileno(fp), &rset);
        continue;
      }
      Writen(sockfd, buf, n);
    }
  }
}
版本一中调用了Fets,Fputs,Readline等有自己缓冲区的函数,select看不到,这将导致缓冲区中的数据来不及消费。
版本二改用了Read,Write,解决这一问题;并且改用了shutdown来关闭连接而不是用close。修复了批量输入的问题。
  • 使用select的服务端程序
#include "unp.h"
int main(int argc, char const *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_port = htons(SERV_PORT);
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  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( ; ; ){
    rset = allset;
    nready = Select(maxfd + 1, &rset, NULL, NULL, NULL);
    if(FD_ISSET(listenfd, &rset)){
      clilen = sizeof(cliaddr);
      connfd = Accept(listenfd, (SA *)&cliaddr, &clilen);
      for(i = 0; i < FD_SETSIZE; i++)
        if(client[i] < 0){
          client[i] = connfd;
          break;
        }
      if(i == FD_SETSIZE)
        err_quit("too many clients");
      FD_SET(connfd, &allset);
      if(connfd > maxfd)
        maxfd = connfd;
      if(i > maxi)
        maxi = i;
      if(--nready <= 0)
        continue;
    }
    for(i = 0; i <= maxi; i++){
      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;
      }
    }
  }
}

实例图:
这里写图片描述

if(--nready <= 0)
    continue;
if(--nready <= 0)
    break;
//这两段代码一开始让我很困惑,这两段代码的作用是提高效率,避免了i到maxi的重复循环。

采用client数组存储连接描述符
使用poll的客户端程序

#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);

第一个参数是指向一个结构数组第一个元素的指针。每个数组元素都是一个pollfd结构,用于指定测试某个给定描述符fd的条件。

struct pollfd{
    int fd;
    short events;
    short revents;
}
回射服务器poll版本:
#include "unp.h"
#include <linux/fs.h>

int main(int argc, char const *argv[]) {
  int i, maxi, listenfd, connfd, sockfd;
  int nready;
  ssize_t n;
  char buf[MAXLINE];
  socklen_t clilen;
  struct pollfd client[INR_OPEN_MAX];
  struct sockaddr_in cliaddr, servaddr;

  listenfd = Socket(AF_INET, SOCK_STREAM, 0);

  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(SERV_PORT);
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));

  Listen(listenfd, LISTENQ);
  client[0].fd = listenfd;
  client[0].events = POLLRDNORM;
  for(i = 1; i < INR_OPEN_MAX; i++)
    client[i].fd = -1;
  maxi = 0;
  for( ; ; ){
    nready = Poll(client, maxi + 1, INFTIM);
    if(client[0].revents & POLLRDNORM){
      clilen = sizeof(cliaddr);
      connfd = Accept(listenfd, (SA*)&cliaddr, &clilen);

      for(i = 1; i < INR_OPEN_MAX; i++){
        if(client[i].fd < 0){
          client[i].fd = connfd;
          break;
        }
      }
      if(i == INR_OPEN_MAX)
        err_quit("too many clients");
      client[i].events = POLLRDNORM;
      if(i > maxi)
        maxi = i;
      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){
            Close(sockfd);
            client[i].fd = -1;
        } else
            Writen(sockfd, buf, n);
      }
        if(--nready <= 0)
            break;
    }
  }
  return 0;
}
总结poll和select的区别:
1) poll不用对最大描述符做+1操作
2)poll在应对大数目的文件描述符时更快,因为select要求内核需要检查大量描述符对应的fd_set中的每一个bit位,比较费时。
3)select可以监控的文件描述符数目是固定的,相对来说教少(1024或2048)。如果需要监控较大的文件描述符或分布较稀疏的的较少的描述符,效率也会很低。而对于poll来说,可以创建特定大小的数组来保存监控的描述符,而不受文件描述符值大小的影响,而且poll可以监控的文件描述符数目远大于select。
http://www.cnblogs.com/Anker/p/3265058.html
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值