epoll是什么?
提到epoll,我们不得不先去了解IO复用下的另外两个重要的机制:select和poll
先看看Linux内核中这三个函数的定义
select
int select(int n,fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
函数解释:
select 函数监视的文件描述符分别是 writefds、readfds、和 exceptfds。调用select 函数会阻塞,直到有描述符就绪(即有数据 可读、可写、或者有 异常),或者超时 (timeout 指定等待时间,如果立即返回设为 null 即可),函数返回。当 select 函数返回后, 可以 通过遍历 fdset,来找到就绪的描述符。
优点:select是最早提出单机上通过一个线程支持多个socket的机制,支持最广泛
缺点:可以通过select函数定义看到,select需要把所有关注事件描述符的集合传入给内核,内核需要遍历所有的描述符,而Linux中单个线程能监视的文件描述符FD上限是1024个,所以效率比较低
poll
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
pollfd 结构包含了要监视的 event 和发生的 event,不再使用 select“参数-值”传递的方 式。同时,pollfd 监控的描述符并没有最大数量限制(但是数量过大后性能也是会下降)。 和 select 函数一样,poll 返回后,需要轮询 pollfd 来获取就绪的描述符。
epoll
int epoll_create(int size);
int epoll_ct(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll可以说是select和poll的加强版,epoll对之前的流程做了分解,分别为:
- epoll_create 创建一个epoll句柄,参数size表示内核监听的初始化值,创建成功后,会占用一个fd,因此在调用完epoll后,记得关闭epoll.
- epoll_ct 对指定的fd执行操作(op),其中,op包括三个操作:添加,删除,修改对fd的监听事件。
- epoll_wait 等待epfd的IO事件,最多返回maxevents个事件
三者的区别
- 支持单个线程打开的最大连接数(监视文件描述符)
| 机制 | 描述 |
|---|---|
| select | 由系统宏(FD_SETSIZE)参数限制,默认一般为1024 |
| poll | 同select |
| epoll | 只受内存限制,因为是基于链表来实现的。 |
- fd数量过多的影响
| 机制 | 描述 |
|---|---|
| select | 每次调用都要线性遍历所有的连接,数量过多会导致效率下降,出现性能问题 |
| poll | 同select |
| epoll | 采用回调机制,只有活跃的socket会回调,不会出现线性下降的性能问题, 但是如果活跃的socket过多的话,也会导致性能下降 |
- 消息传递方式
| 机制 | 描述 |
|---|---|
| select | 需要将消息从内核态传递到用户态,即内核copy动作,涉及到内核态到用户态的切换 |
| poll | 同select |
| epoll | 内核和用户空间共享 |
总结
表面来看,epoll的性能最优,但是也要分场景,在连接数少并且连接都活跃的情况下,select和poll的表现要优于epoll,因为epoll需要很多的函数回调,而select, poll没有这个环节,所以epoll在连接数多的场景性能优势会非常明显。
版权声明:本文为beyond_0818原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。