Linux I/O多路复用详解及实例

2019-10-13 18:20:34王旭

poll机制

poll是System V提出的一种基于select的改良机制,其针对select的诸多明显的缺陷进行了重新设计,包括只遍历被触发个数个文件描述符,不需要备份fd_set等等

模型

struct pollfd  fds   //创建一个pollfd类型的数组
fds[0].fd        //向fds[0]中放入需要监视的fd
fds[0].events      //向fds[0]中放入需要监视的fd的触发事件
  POLLIN       //I/O有输入
  POLLPRI       //有紧急数据需要读取
  POLLOUT       //I/O可写
  POLLRDHUP      //流式套接字连接断开或套接字处于半关闭状态
  POLLERR       //错误条件(仅针对输出)
  POLLHUP       //挂起(仅针对输出)
  POLLNVAL      //无效的请求:fd没有被打开(仅针对输出)

例子_I/O多路复用并发服务器

/* ... */

int main()
{
  /* ... */
  struct pollfd myfds[MAXNFD] = {0};
  myfds[0].fd = listenfd;
  myfds[0].events = POLLIN;
  int maxnum = 1;
  
  int nready;
  //准备二维数组buf,每个fd使用buf的一行,数据干扰
  char buf[MAXNFD][BUFSIZE] = {0};
  while(1){
    //poll直接返回event被触发的fd的个数
    nready = poll(myfds, maxnum, -1)
    int i = 0;
    for(;i<maxnum; i++){
      //poll通过将相应的二进制位置一来表示已经设置
      //如果下面的条件成立,表示revent[i]里的POLLIN位已经是1了
      if(myfds[i].revents & POLLIN){
        if(myfds[i].fd == listenfd){
          int sockfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
          //将新accept的scokfd加入监听集合
          myfds[maxnum].fd = sockfd;
          myfds[maxnum].events = POLLIN;
          maxnum++;
          
          //如果意见检查了nready个fd,就直接下一个循环
          if(--nready==0)
            continue;
        }
        else{
          int ret = read(myfds[i].fd, buf[myfds[i].fd], sizeof buf[0]);
          if(0 == ret){  //如果连接断开了
            close(myfds[i].fd);
            
             //初始化将文件描述符表所有的文件描述符标记为-1
             //close的文件描述符也标记为-1
             //打开新的描述符时从表中搜索第一个-1
             //open()就是这样实现始终使用最小的fd
             //这里为了演示并没有使用这种机制
             myfds[i].fd = -1; 
            continue;
          }
          myfds[i].events = POLLOUT;
        }
      }
      else if(myfds[i].revents & POLLOUT){
        int ret = write(myfds[i].fd, buf[myfds[i].fd], sizeof buf[0]);
        myfds[i].events = POLLIN;
      }
    }
  }
  close(listenfd);
}

epoll

epoll在poll基础上实现的更为健壮的接口,也是现在主流的web服务器使用的多路复用技术,epoll一大特色就是支持EPOLLET(边沿触发)和EPOLLLT (水平触发),前者表示如果读取之后缓冲区还有数据,那么只要读取结束,剩余的数据也会丢弃,而后者表示里面的数据不会丢弃,下次读的时候还在,默认是EPOLLLT