彻底理解 IO 多路复用实现机制( 四 )

poll使用示例// 先宏定义长度#define MAX_POLLFD_LEN 4096int main() {/** 在这里进行一些初始化的操作 ,* 比如初始化数据和socket等 。*/int nfds = 0;pollfd fds[MAX_POLLFD_LEN];memset(fds, 0, sizeof(fds));fds[0].fd = listenfd;fds[0].events = POLLRDNORM;int max= 0;// 队列的实际长度 , 是一个随时更新的 , 也可以自定义其他的int timeout = 0;int current_size = max;while (1) {// 阻塞获取// 每次需要把fd从用户态拷贝到内核态nfds = poll(fds, max+1, timeout);if (fds[0].revents//将新的描述符添加到读描述符集合中}// 每次需要遍历所有fd , 判断有无读写事件发生for (int i = 1; i < max; ++i) {if (fds[i].reventsfds[i].fd = -1;}} else {// 这里处理write事件}if (--nfds <= 0) {break;}}}}poll缺点它没有最大连接数的限制 , 原因是它是基于链表来存储的 , 但是同样有缺点:

  • 每次调用 poll, 都需要把 fd 集合从用户态拷贝到内核态 , 这个开销在 fd 很多时会很大;
  • 对 socket 扫描是线性扫描 , 采用轮询的方法 , 效率较低(高并发时)
epollepoll可以理解为event poll , 不同于忙轮询和无差别轮询 , epoll会把哪个流发生了怎样的I/O事件通知我们 。 所以我们说epoll实际上是**事件驱动(每个事件关联上fd)**的 , 此时我们对这些流的操作都是有意义的 。 (复杂度降低到了O(1))
epoll函数接口当某一进程调用epoll_create方法时 , Linux内核会创建一个eventpoll结构体 , 这个结构体中有两个成员与epoll的使用方式密切相关 。 eventpoll结构体如下所示:
#include // 数据结构// 每一个epoll对象都有一个独立的eventpoll结构体// 用于存放通过epoll_ctl方法向epoll对象中添加进来的事件// epoll_wait检查是否有事件发生时 , 只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可struct eventpoll {/*红黑树的根节点 , 这颗树中存储着所有添加到epoll中的需要监控的事件*/struct rb_rootrbr;/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/struct list_head rdlist;};// APIint epoll_create(int size); // 内核中间加一个 ep 对象 , 把所有需要监听的 socket 都放到 ep 对象中int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // epoll_ctl 负责把 socket 增加、删除到内核红黑树int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);// epoll_wait 负责检测可读队列 , 没有可读 socket 则阻塞进程每一个epoll对象都有一个独立的eventpoll结构体 , 用于存放通过epoll_ctl方法向epoll对象中添加进来的事件 。 这些事件都会挂载在红黑树中 , 如此 , 重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn , 其中n为红黑树元素个数) 。
而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系 , 也就是说 , 当相应的事件发生时会调用这个回调方法 。 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中 。
在epoll中 , 对于每一个事件 , 都会建立一个epitem结构体 , 如下所示:
struct epitem{struct rb_noderbn;//红黑树节点struct list_headrdllink;//双向链表节点struct epoll_filefdffd;//事件句柄信息struct eventpoll *ep;//指向其所属的eventpoll对象struct epoll_event event; //期待发生的事件类型}当调用epoll_wait检查是否有事件发生时 , 只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可 。 如果rdlist不为空 , 则把发生的事件复制到用户态 , 同时将事件数量返回给用户 。


推荐阅读