ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Select Poll Epoll

2020-01-13 17:03:27  阅读:239  来源: 互联网

标签:poll epoll int Epoll 描述符 fd Select Poll select


上一篇文章我们已经介绍过了集中常用的 IO 模型了。IO 多路复用模型是我们用的最多的一种 IO 模型。select,poll,epoll 都是 IO 多路复用的机制。I/O 多路复用就是通过一种机制,一个进程可以监视多个描述符。一旦某个描述符就绪(读或者写就绪),就通知程序进行相应的读写操作。

select,poll,epoll 本质上都是同步 I/O,因为它们都需要在读写事件就绪后自己负责读写,也就是说,这个读写过程是阻塞的,而异步 I/O 则无需自己负责进行读写,异步 I/O 实现会负责把数据从内核拷贝到用户空间。

select

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 函数监视的文件描述符分为三类,分别是 writefds,readfds 和 exceptfds。调用后 select 函数会阻塞,直到有文件描述符就绪(有数据可读,可写或者有 except),或者超时(timeout 指定等待时间,如果立即返回设置为 null 即可),函数返回。当 select 函数返回后,可以通过遍历 fdset,来找到就绪的描述符。

select 目前几乎在所有的平台上支持,其良好的跨平台也成为它的有点。select 的一个缺点在于单个进程能够监视的文件描述符最大为 1024。

select 本质上是通过设置或者检查存放 fd 标志位的数据结构来进行下一步处理。这样所带来的缺点是:

  1. 单个进程可监视的 fd 数量被限制,即能监听端口的大小有限。32 位是 1024,64 位是 2048。
  2. 对 socket 进行扫描时都是线性扫描,即采用轮询的方法,效率比较低。不管是不是活跃的,都选哟遍历一遍。
  3. 需要维护一个用来存放大量 fd 的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。

poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

不同于 select 使用三个位图来表示 fdset 的方式,poll 使用也给 pollfd 的指针实现。

struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
};

pollfd 结构包含了要监视的 event 和发生的 event,不再使用 select “参数-值”传递的方式。同时,pollfd 并没有最大数量限制(但是数量过大后性能也是会下降)。和 select 函数一样,poll 返回后,需要轮询 pollfd 来获取就绪的描述符。

select 和 poll 都需要在返回后,通过遍历文件描述符来获取已经就绪的 socket。事实上,同时连接的大量客户端在同一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

poll 本质上和 select 并没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个 fd 对应的设备状态,如果设备就绪则在设备等待队列加入一项并继续遍历,如果遍历完所有 fd 后没有发现就绪设备,则挂起当前线程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历 fd。

  1. poll 没有连接数限制,原因它是基于链表来存储的。
  2. 也是需要轮询来查询 fd 的状态,fd 越多,效率越差。

epoll

epoll 是在 2.6 内核中提出的,是之前的 select 和 poll 的增强版本。相对于 select 和 poll 来说,epoll 更加灵活,没有描述符限制。epoll 使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的 copy 只需一次。

epoll 操作过程

一个 epoll 操作过程需要三个接口,分别如下:

int epoll_create(int size);//创建一个epoll的句柄,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);

int epoll_create(int size)

创建一个 epoll 的句柄,size 用来告诉内核这个监听的数目一共多大,这个参数不同于 select() 中的第一个参数,给出最大监听的 fd+1 的值,参数 size 并不是限制了 epoll 所能监听描述符的最大个数,只是对内核初始分配内部数据结构的一个建议。

创建好 epoll 句柄后,它就会占用一个 fd 值,使用完 epoll 后,必须调用 close() 关闭,否则可能导致 fd 被耗尽。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

函数是对指定描述符 fd 执行 op 操作。

  • epfd: 是 epoll_create() 返回值。
  • op: 表示 op 操作,用三个宏来表示:添加 EPOLL_CTL_ADD, 删除 EPOLL_CTL_DEL, 修改 EPOLL_CTL_MOD。分别添加,删除和修改对 fd 的监听事件。
  • fd: 需要监听的 fd(文件描述符)。
  • epoll_event: 是告诉内核需要监听什么事,struct epoll_event 的结构如下:
struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};

//events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)

等待 epfd 上的 io 事件,最多返回 maxevents 个事件。

参数 events 用来从内核得到事件的集合,maxevents 告之内核这个 events 有多大。该函数返回需要处理的事件数目,如返回 0 表示已超时。

工作模式

epoll 对文件描述符的操作有两种模式:LT(level trigger)和 ET(edge trigger)。

LT 模式(默认):当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用 epoll_wait 时,会再次响应应用程序并通知此事件。通过这种模式,系统不会充斥大量你不关心的就绪文件描述符。

ET模式(告诉):当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理。如果不处理,下次调用 epoll_wait 时,不会再次响应应用程序并通知此事件。在 ET 模式下,read 一个 fd 的时候一定要把它的 buffer 都读光。

  1. epoll 没有最大并发连接的限制,能打开的 FD 的上线远大于 1024。
  2. 效率提升,不是轮询的方式,不会随着 FD 数目的增加效率下降,只有活跃可用的 FD 才会调用 callback 函数。
  3. epoll 最大的有点在于它只管你活跃的连接,而跟连接总数无关。因此在实际的网络环境中,epoll 的效率会远高于 select 和 poll。
  4. 内存拷贝,利用 mmap() 文件映射内存加速与内核空间的消息传递,即 epoll 使用 mmap 减少复制开销。

select,poll 和 epoll 的区别总结

  1. 支持一个进程能打开的最大连接数。

    select 是 1024,poll 没有最大限制,底层是基于链表的。epoll 有上限,但hi很大,1G 内存的及其可以打开 10 万左右的连接,2G 内存可以打开 20 万左右的连接。

  2. FD 剧增后带来的 IO 效率问题。

    select 和 poll 每次调用都会对 FD 进行线性遍历,所以随着 FD 增加遍历的速度会越慢。

    因为 epoll 内核中的实现是根据每个 fd 上的 callback 函数来上西安的,只有活跃的 socket 才会主动 callback,所以性能和总的监视的文件描述符无关。

  3. 消息传递方式。

    select 和 poll 内核需要将消息传递到用户空间,都需要内核拷贝动作。epoll 通过内核和用户共享一块内存来实现。

select poll epoll
操作方式 遍历 遍历 回调
底层实现 数组 链表 哈希表
IO效率 每次调用都进行线性遍历,时间复杂度为O(n) 每次调用都进行线性遍历,时间复杂度为O(n) 事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1)
最大连接数 1024(x86)或2048(x64) 无上限 无上限
fd拷贝 每次调用select,都需要把fd集合从用户态拷贝到内核态 每次调用poll,都需要把fd集合从用户态拷贝到内核态 调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝

总结

表面上看 epoll 的性能最好,但是在连接数少并且都十分活跃的情况下,select 和 poll 的性能可能比 epoll 好,毕竟 epoll 的通知机制需要很多函数回调。

select 低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可以通过良好的设计改善。

标签:poll,epoll,int,Epoll,描述符,fd,Select,Poll,select
来源: https://www.cnblogs.com/paulwang92115/p/12188114.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有