博客内容:多路转发的常见方式select,poll,epoll
前言
Linux下一切皆文件,是文件就会存在IO的情况,IO的方式决定了效率的高低。
可以说IO就是等待+读写。IO效率取决于等待的时间。
函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout)
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
最重要的一点,在使用select函数时,用户需要传递一个数组作为参数,用于存储可读、可写和错误事件的文件描述符集合。这是因为操作系统内核不知道用户需要监视哪些文件描述符,因此用户需要自己维护一个数组来告诉内核要监视哪些文件描述符。而且这个数组需要在每次调用select函数时重新初始化,告诉内核新的监视对象。因此,select需要用户自己维护数组。
poll函数原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// pollfd结构
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
参数说明
epoll是为了处理大批量的句柄而改进的poll。
使用epoll的三部曲
epoll_create
创建一个epoll句柄;epoll_ctl
将要监控的文件描述符进行注册;epoll_wait
等待文件描述符就绪int epoll_create(int size);
参数:自从linux2.6.8之后, size参数是被忽略的.用完之后, 必须调用close()关闭。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:第一个就是epoll模型,epoll_create()的返回值。参数二是表示的动作,通常使用宏来表示
- EPOLL_CTL_ADD :注册新的fd到epfd中;
- EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
- EPOLL_CTL_DEL :从epfd中删除一个fd
参数三是需要监听的文件描述符,参数四是监听文件描述符的什么事件。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 参数events是分配好的epoll_event结构体数组
- .epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存).
- maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size.
- 参数timeout是超时时间 (毫秒, 0会立即返回,-1是永久阻塞).如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败。
为啥epoll好?
通过与select的缺点对应比较。
- 接口方便:虽然拆分了三个函数,但是更加高效,使用select时需要每次都访问自己维护的数组。输入输出的参数进行了分离。
- 数据拷贝更加的轻量化:在对于文件描述符的添加时不是一直都都需要调用EPOLL_CTL_ADD进行拷贝到内核中的,不会像select一样操作频繁。
- 事件回调机制:避免使用了遍历,使用的是回调函数机制,就绪的文件描述符结构加入就绪队列中,epoll_wait返回直接访问就绪队列的就是知道哪些文件描述符已经就绪。事件复杂度是O(1)的。
- 没有数量上的限制。
以上的优点何以见得,epoll的底层原理就是
每一个epoll对象都有一个独立的eventpoll的结构体,用来存放epoll_ctl方法向epoll对象中添加进来的事件。事件挂载到红黑树上,重复添加就是对于红黑树的节点的增,查,改操作。添加的事件都会与外界的设备程序建立回调关系,事件一旦发生就会触发对应的回调函数。回调的方法在内核中ep_poll_callback,它再将发生的事件添加到rdilist双向链表中,双向链表作为epoll_wait的消息队列。对于同一个事件节点是被红黑树和消息队列的俩个数据结构共用的。
三者之间的对比
特性 | select | poll | epoll |
---|---|---|---|
可监视的fd数 | 最大为FD_SETSIZE | 没有限制 | 没有限制 |
事件触发方式 | 轮询 | 轮询 | 事件通知 |
I/O效率 | 低 | 一般 | 高 |
内存占用 | 高 | 适中 | 低 |
时间复杂度 | O(n) | O(n) | O(1) |
可移植性 | 跨平台 | 跨平台 | 仅在Linux、BSD等少数平台支持 |
文件描述符生命周期控制 | 子进程不继承fd | 子进程不继承fd | 子进程自动继承fd |
select适用于连接数较少的情况,可移植性好,但效率较低;poll效率高于select,但仍然存在遍历fd集合的缺点;epoll适用于连接数较多的场景,且能够避免无用的遍历,效率最高,但只能运行在Linux系统上。