2022-08-15 22:26阅读: 285评论: 0推荐: 0

IO复用--select、poll、epoll、kqueue

IO复用

  • 目标:用单线程去处理多个IO请求。
  • 文件描述符:是非负整数。它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。
    • 每一个进程都有一个文件描述符表。不同进程可以拥有相同文件描述符。相同文件描述符可以指向同一文件。
    • 文件描述符可以与File*相互转化。
  • web服务器项目实现:https://github.com/oniisancr/webserver

1 select

copy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
/*** * maxfd 监视对象文件描述符数量 * readset 将所有关注"是否存在待读取数据"的文件描述符注册到fd_set型变量,并传递其地址值。 * writeset 将所有关注"是否可传输无阻塞数据"的文件描述符注册到fd_set型变量,并传递其地址值。 * exceptset 将所有关注"是否发生异常"的文件描述符注册到fd_set型变量,并传递其地址值。 * timeout 调用select函数后,为防止陷入无限阻塞的状态,传递超时(time-out)信息。 * return 发生错误时返回-1,超时返回时返回0。因发生关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符数。 */ int select(int maxfd, fd_set * readset, fd_set * writeset, fd_set * exceptset, struct timeval * timeout);
  • 阻塞式IO复用
  • 因为select函数可以同时监视多个文件描述符,可以将多个文件描述符集中到一起统一监视
  • 步骤
    • select函数调用前的所有准备工作
      • 设置文件描述符集合,再设置对应的fd_set变量,对应位置为1. 使用FD_ZERO、FD_SET等函数。
      • 设定监视范围,取文件描述符最大值加1。设置超时。
    • 调用select函数、最后查看结果。
      • fd_set变量中仍然为1的对应位置上的文件描述符发生了变化。使用FD_ISSET函数。
  • 缺点
    • FD_SETSIZE为1024,即最多同时监视1024个文件描述符。
    • select函数后需要遍历fd_set。
    • 每次select函数需要传递监视对象的信息。
      • 因为套接字是由操作系统管理,所以每次需要将该信息传递给操作系统,开销很大。用户态-->内核态。
      • 可以通过只传递一次监视对象,监视范围和内容发生变化时,只通知变化的事项。这样就元需每次调用 select函数时都向操作系统传递监视对象信息。Linux使用epoll,windows是IOCP。
  • 适用场景:服务器端接入者少;程序要求兼容性。

2 poll

copy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */ }; /*** * nfds 监听对象个数 * timeout 超时时间。设置为-1代表永久阻塞,直到有就绪事件的发生 */ int poll (struct pollfd *fds, unsigned int nfds, int timeout);
  • 和select类似,只是描述fd集合的方式不同,poll使用pollfd结构而非select的fd_set结构。
  • 步骤
    • poll函数调用前的所有准备工作
      • 设置pollfd结构体,events是触发事件。
      • 设置监视数目。设置超时时间。
    • 调用poll,查看结果
      • pollfd结构体中revents为1的,表示触发了事件。
      • 需要手动恢复revents成0
  • 缺点
    • poll函数后需要遍历pollfd结构体。
    • 每次poll函数需要传递监视对象的信息。
      • 仍然存在用户态到内核态数据的整体复制。
  • 对比select的改进
    • 不要求用户计算编号最高的fd_max+1的值
    • 对于编号大的文件描述符更有效。如一个单900的文件描述符,select需要判断900位,而poll只需要一次。
    • 没有最大文件描述符数量的限制,select文件描述符小于1024。
    • 由于采用新的数据结构,每次处理完任务后将revents置为0后,则可以继续重用原结构体。

3 epoll

Linux uses epoll

copy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
struct epoll_event { uint32_t events; epoll_data_t data; }; // size是对系统的建议值 int epoll_create(int size); /** epfd 用于注册监视对象的 epoll例程的文件描述符。 op 指定监视对象的添加、删除或更改等操作 fd 需要注册的监视对象文件描述符 event 监视对象的事件类型 */ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); /** events 保存发生事件的文件描述符集合的结构体地址值 maxevents 第二个参数中可以保存的最大事件数 timeout 以1/1000秒为单位的等待时间,传递-1时,一直等待直到发生事件 return 成功时返回发生事件的文件描述符数,失败时返回-1,超时返回0 */ int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • 需要调用的三个函数
    • epoll_create 创建保存epoll文件描述符的空间
    • epoll_ctl 向空间注册并注销文件描述符
    • epoll_wait 与select函数类似,等待文件描述符发生变化
  • 条件触发(水平触发):在条件触发方式中,只要输入缓冲有数据就会一直通知该事件。
    • 如输入缓冲中有50字节,操作系统会通知该事件。当读完30字节还剩20字节时,仍会注册该事件。
    • epoll默认以条件触发方式工作。
  • 边缘触发中输入缓冲收到数据时仅注册1次该事件。不管读没读取完。
    • 一定要采用非阻塞read、write,否则可能会引起长时间停顿
    • 读完数据后情况为:str_len=-1,errno=EAGAIN
  • 优点:
    • 仅向操作系统传递1次监视对象监视范围或内容发生变化时只通知发生变化的事项。不需要频繁拷贝。

4 kqueue

BSD and OSX use kqueue

copy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
struct kevent { uintptr_t ident; //事件标识 short filter; // 监听事件的类型,如EVFILT_READ,EVFILT_WRITE,EVFILT_TIMER等 u_short flags; //事件操作类型,如EV_ADD,EV_ENABLE,EV_DELETE等 u_int fflags; /* filter flag value */ intptr_t data; /* filter data value */ void *udata; //可携带的任意用户数据 }; //类似epoll_create int kqueue(void); /** * 相当于组合了epoll_ctl及epoll_wait的功能 * kq 注册的kqueue * changelist 要监督的事件列表 * nchanges changelist监听事件的长度 * eventlist 用于返回已经就绪的事件列表 * nevents eventlist列表最大长度 * timeout 超时时间 设置nullptr为一直等待 * return events就绪的数目 */ int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout); //设定kevent参数的宏,用来初始化kevent结构体 EV_SET(&kev, ident, filter, flags, fflags, data, udata);
  • 步骤
    • 调用kqueue,创建kq
    • EV_SET 初始化kevent结构体,以及相关参数
    • kevent函数,与epoll_wait类似。eventlist中为触发的事件,返回值为其长度。
  • 默认条件触发,event设置为EV_CLEAR后为边缘触发。

5 IOCP

Windows uses IOCP

...

非阻塞套接字设置

copy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
// Linux以提供的更改或读取文件属性的 /** * 成功时返回cmd参数相关值,失败时返回-1 * 若第二个参数传递F_GETFL,获得第一个参数所指的文件描述符属性 * 若第二个参数传递F_SETFL,可以更改文件描述符属性 */ int fcntl(int filedes, int cmd, . . . );
  • 将套接字改为非阻塞式,则需要以下两句
copy
  • 1
  • 2
  • 3
  • 4
//获取之前设置的属性信息 int flag = fcntl(fd,F_GETFL,0); //添加非阻塞 O_NONBLOCK标志 fcntl(fd, F_SETFL, flag|O_NONBLOCK);

reference

链接[1]包含实例代码

本文作者:Oniisan_Rui

本文链接:https://www.cnblogs.com/oniisan/p/linux-io-multiplexing-select-vs-poll-vs-epoll.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Oniisan_Rui  阅读(285)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起