前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >IO复用 知识点梳理

IO复用 知识点梳理

原创
作者头像
windealli
发布2019-11-01 19:22:08
7750
发布2019-11-01 19:22:08
举报
文章被收录于专栏:windealli

IO复用

程序可以同时监听多个fd

场景

  • 需要同时监听多个socket
  • 需要同时监听socket和用户输入
  • 需要同时处理监听socket和连接socket
  • 需要同时处理TCP和UDP
  • 需要同时监听多个端口

socket就绪

  • 可读
  • 内核缓冲区收到的数据>=其低水位标志
  • 对方关闭连接
  • socket上有新的连接请求
  • socket上有未处理的错误
  • 可写
  • 发送缓冲区的字节数大于等于SO_SNDLOWAT
  • 关闭写操作
  • 非阻塞connect连接成功或失败
  • socket上有未处理的错误
  • 异常
  • socket接收到带外数据

XMind: ZEN - Trial Version

select

select API模型:

代码语言:txt
复制
       int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 用三个fd数组分别表示要监听可读、可写、异常事件的描述符数组。

readfds、writefds、exceptfds这三个数组既是输入参数,也是输出参数。 它们也用于内核空间想用户空间传递就绪的文件描述符。

select使用模型:

代码语言:txt
复制
          while(1) {                                           
              FD_ZERO(&rfds);                                  
              FD_ZERO(&wfds);                                  
              FD_SET(fd0, rfds); //                            
              FD_SET(fd1, wfds); //                            
              ret = select(MAX_FDS+1, &rfds, &wfds, &efds, &tv);                                                                  
              if (FD_ISSET(fd0, &rfds)) {                       
                  ...                                          
              } else (FD_ISSET(fd1, &wfds)) {                   
                  ...                                          
              }                                                
          }     

select调用时,会通过三个数组参数把要监听的文件描述符(和对应的事件)传递给内核。

select因有就绪事件而返回时,内核再把相应就绪的文件描述符通过三个数组返回。

此时程序需要遍历监听文件描述符,判断其是否在相应的就绪fds中。

缺陷

select模型有如下缺陷

  1. 因为只有一个函数,所以每次调用文件都需要把监听的文件描述符这些数据从用户空间拷贝到内核空间。
  2. 由于rfds、wfds、efds既是入参也是出参,所以每次调用select都需要重新设置三个数组。
  3. 索引就绪fd时,仍然需要遍历所有监听的fds,做FD_ISSET
  4. 由fd_set能容纳的文件描述符数量限制为FD_SETSIZE,这个值被设置为1024
  5. select只支持 可读、可写、异常 三类事件。

poll

poll API原型:

代码语言:txt
复制
     #include <poll.h>

     int poll(struct pollfd fds[], nfds_t nfds, int timeout);




     struct pollfd {

         int    fd;       /* file descriptor */

         short  events;   /* events to look for */

         short  revents;  /* events returned */

     };

poll函数讲描述符和事件区关联起来了。 同时讲注册到事件和就绪到事件区分开来。 内核只需要修改revents。

poll 使用:

代码语言:txt
复制
int main() {
    int ret = 0;
    struct pollfd pollfds[1];
    char buf[10];
    pollfds[0].fd = 1;
    pollfds[0].events = POLLIN;
        
    while(1) {
        memset(buf, 0, sizeof(buf));
        ret = poll(pollfds, 2, 0); 
        if (pollfds[0].revents == POLLIN) {
            scanf("%s", buf);
            printf("buf:%s\n", buf);
        }   
    }   
    return 0;                                                                                                                     
}       

可以看出,相比select函数,poll把监听描述符和事件到设置从for循环中移动出来了。

优缺点

相对select的改进

  1. 相对于select,不必每次调用前都重置一次监听参数pollfds,
  2. 相对于select,能够监听更多都事件类型。

仍然存在都不足:

  1. 因为只有一个函数,所以每次调用文件都需要把监听的文件描述符这些数据从用户空间拷贝到内核空间。
  2. 索引就绪fd时,仍然需要遍历所有监听的fds,

epoll

epoll 是linux特有的API

epoll API

代码语言:txt
复制
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
代码语言:txt
复制
           typedef union epoll_data {
               void *ptr;
               int fd;
               uint32_t u32;
               uint64_t u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t events; /* Epoll events */
               epoll_data_t data; /* User data variable */
           };

与select和poll只有一个函数不同,epoll API提供了一组函数来实现IO复用。

epoll 使用模型

代码语言:txt
复制
    struct epoll_event event;
    struct epoll_event *events; // 用户保存内核返回的就绪事件
    int efd;
    int listenfd = 0; // 监听标准输入
              
    efd = epoll_create(0);
              
    event.data.fd = listenfd;
    event.events = EPOLLIN | EPOLLET;
    ret = epoll_ctl (efd, EPOLL_CTL_ADD, listenfd, &event);
              
    while(1) {
       int n, i;
        n = epoll_wait(efd, events, MAXEVENTS, -1);
        for (i = 0; i < n; i++) {
            if (events[i].events & EPOLLIN) {
                // handle EpollIN, 可以从events[i].data.fd获取文件描述符
                                                                                                                              
        }   
    } 

优缺点

epoll所做的改进

  1. epoll 提供了一整组函数, 将监听数据(epoll_ctl)的传递和监听本身(epoll_wait)分开了。调用epoll_wait时,不需要在将要监听都文件描述符、事件等从用户空间拷贝到内核空间。
  2. epoll事件返回了就绪到文件描述符和对应到事件,因而索引就绪事件时不需要遍历所有监听事件。事件复杂度变为O(1)。
  3. 在内部实现上,epoll使用回调取代了select和poll到轮询机制,极大提升了性能。

其他

LT和ET

文件描述符的操作有LT和ET两种方式,epoll两种模式都支持。

  • LT:电平触发
代码语言:txt
复制
    epoll将就绪事件通知应用程序后,应用程序可以不用立即处理。 下次调用epoll_wait的时候,还会继续通知。    
  • ET: 边缘触发
代码语言:txt
复制
    epoll将就绪事件通知应用程序后,应用程序**必须**立即处理。 下次调用epoll_wait的时候,不会重复通知。    

举个栗子,如果是ET模式,epoll_wait检测到事件可读性,通知应用程序后,应用程序就需要吧本次可读读数据都读完。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • IO复用
    • 程序可以同时监听多个fd
      • 场景
        • socket就绪
        • select
          • 缺陷
          • poll
            • poll API原型:
              • poll 使用:
                • 优缺点
                • epoll
                  • epoll API
                    • epoll 使用模型
                      • 优缺点
                      • 其他
                        • LT和ET
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档