前段时间被迫把一坨没人维护代码中的Select机制换成了Epoll,借此机会梳理一下Select、Poll和Epoll的原理。
I/O多路复用:
I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
Select:
函数原型和使用:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
void FD_ZERO(fd_set *fdset);//清空集合
void FD_SET(int fd, fd_set *fdset);//将一个给定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset);//将一个给定的文件描述符从集合中删除
int FD_ISSET(int fd, fd_set *fdset);// 检查集合中指定的文件描述符是否可以读写
缺点:
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,也需要在内核遍历传递进来的所有fd,开销过大
- select支持的文件描述符数量过小,默认是1024,且调大需要重新编译系统内核
Poll:
函数原型和使用:
# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
/*Poll结构体*/
struct pollfd {
int fd;/* 文件描述符 */
short events;/* 等待的事件 */
short revents;/* 实际发生了的事件 */
};
缺点:
- 只解决了select描述符上限的问题,没解决效率问题。
Epoll:
epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
函数原型和使用:
#include <sys/epoll.h>
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);
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
缺点:
- 使用较为复杂
简单的总结:
对比 | Select | Poll | Epoll |
---|---|---|---|
机制 | 轮询 | 轮询 | 通知 |
默认FD上限 | 1024 | 无上限 | 无上限 |
效率 | 低 | 低 | 高 |
参考资料:
https://www.cnblogs.com/anker/p/3265058.html
https://blog.csdn.net/qq_39612543/article/details/121963681