当前位置:首页 » 《随便一记》 » 正文

【在Linux世界中追寻伟大的One Piece】多路转接epoll

25 人参与  2024年11月13日 19:20  分类 : 《随便一记》  评论

点击全文阅读


目录

1 -> I/O多路转接之poll

1.1 -> poll函数接口

1.2 -> poll的优点

1.3 -> poll的缺点

1.4 -> poll示例

1.4.1 -> 使用poll监控标准输入

2 -> I/O多路转接之epoll

2.1 -> 初识epoll

2.2 -> epoll的相关系统调用

2.2.1 -> epoll_create

2.2.2 -> epoll_ctl

2.2.3 -> epoll_wait

2.3 -> epoll工作原理

2.4 -> epoll优点


1 -> I/O多路转接之poll

1.1 -> poll函数接口

#include <poll.h>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 */};

参数说明:

fds是一个poll函数监听的结构列表。每一个元素中,包含了三部分内容:文件描述符,监听的事件集合,返回的事件集合。nfds表示fds数组的长度。timeout表示poll函数的超时时间,单位是毫秒(ms)。

events和revents的取值:

事件描述是否可作为输入是否可作为输出
POLLIN数据(包括普通数据和优先数据)可读
POLLRDNORM普通数据可读
POLLRDBAND优先级带数据可读(Linux不支持)
POLLPRI高优先级数据可读,比如TCP带外数据
POLLOUT数据(包括普通数据和优先数据)可写
POLLWRNORM普通数据可写
POLLWRBAND优先级带数据可写

POLLRDHUPTCP连接被对方关闭,或者对方关闭了写操作。它由GNU引入
POLLERR错误

POLLHUP挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件
POLLNVAL文件描述符没有打开

返回结果:

返回值小于0,表示出错。返回值等于0,表示poll函数等待超时。返回值大于0,表示poll由于监听的文件描述符就绪而返回。

1.2 -> poll的优点

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

pollfd结构包含了要监视的event和发生的event,不再使用select"参数-值"传递的方式。接口使用比select更方便。poll并没有最大数量限制(但是数量过大后性能也是会下降)。

1.3 -> poll的缺点

poll中监听的文件描述符数目增多时:

和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中。同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

1.4 -> poll示例

1.4.1 -> 使用poll监控标准输入

#include <poll.h>#include <unistd.h>#include <stdio.h>int main() {struct pollfd poll_fd;poll_fd.fd = 0;poll_fd.events = POLLIN;for (;;) {int ret = poll(&poll_fd, 1, 1000);if (ret < 0) {perror("poll");continue;}if (ret == 0) {printf("poll timeout\n");continue;}if (poll_fd.revents == POLLIN) {char buf[1024] = { 0 };read(0, buf, sizeof(buf) - 1);printf("stdin:%s", buf);}}}

2 -> I/O多路转接之epoll

2.1 -> 初识epoll

epoll是Linux内核中提供的一种高效的IO多路复用机制,它专为处理大量文件描述符而设计。相比于传统的select和poll机制,epoll在存在大量并发连接且只有少数连接活跃时,能够显著提高系统的CPU利用率。epoll的关键优势在于它在获取就绪事件时,不会遍历所有被监听的文件描述符集,而是只会遍历那些被设备IO事件异步唤醒(通过CPU中断机制)而加入就绪链表的文件描述符集。

按照man手册的说法:是为处理大批量句柄而作了改进的poll。

它是在2.5.44内核中被引进的(epoll(4)is a new API introduced in Linux kernel 2.5.44)它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。

2.2 -> epoll的相关系统调用

2.2.1 -> epoll_create

int epoll_create(int size);

创建一个epoll的句柄。

自从linux2.6.8之后,size参数是被忽略的。用完之后,必须调用close()关闭。

2.2.2 -> epoll_ctl

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

epoll的事件注册函数。

它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

第一个参数是epoll_create()的返回值(epoll 的句柄)。第二个参数表示动作,用三个宏来表示。第三个参数是需要监听的fd。第四个参数是告诉内核需要监听什么事。

第二个参数的取值:

EPOLL_CTL_ADD:注册新的fd到epfd中。EPOLL_CTL_MOD:修改已经注册的fd的监听事件。EPOLL_CTL_DEL:从epfd中删除一个fd。

struct epoll_event结构如下:

typedef union epoll_data{void* ptr;int fd;uint32_t u32;uint64_t u64;} epoll_data_t;struct epoll_event{uint32_t events; //Epoll eventsepoll_data_t data; //User data variable}_EPOLL_PACKED;

events可以是以下几个宏的集合:

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

2.2.3 -> epoll_wait

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

收集在epoll监控的事件中已经发送的事件。

参数events是分配好的epoll_event结构体数组。epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size。参数timeout是超时时间(毫秒,0会立即返回,-1是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败。

2.3 -> epoll工作原理

当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。

struct eventpoll {/*红黑树的根节点,这颗树中存储着所有添加到 epoll 中的需要监控的事件*/struct rb_root rbr;/*双链表中则存放着将要通过 epoll_wait 返回给用户的满足条件的事件*/struct list_head rdlist;};
每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。在epoll中,对于每一个事件,都会建立一个epitem结构体。
struct epitem {struct rb_node rbn;//红黑树节点struct list_head rdllink;//双向链表节点struct epoll_filefd ffd; //事件句柄信息struct eventpoll* ep; //指向其所属的 eventpoll 对象struct epoll_event event; //期待发生的事件类型}
当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。这个操作的时间复杂度是O(1)。

总结一下,epoll的使用过程就是三部曲:

调用epoll_create创建一个epoll句柄。调用epoll_ctl,将要监控的文件描述符进行注册。调用epoll_wait,等待文件描述符就绪。

2.4 -> epoll优点

接口使用方便:虽然拆分成了三个函数,但是反而使用起来更方便高效。不需要每次循环都设置关注的文件描述符,也做到了输入输出参数分离开。数据拷贝轻量:只在合适的时候调用EPOLL_CTL_ADD将文件描述符结构拷贝到内核中,这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)。事件回调机制:避免使用遍历,而是使用回调函数的方式,将就绪的文件描述符结构加入到就绪队列中,epoll_wait返回直接访问就绪队列就知道哪些文件描述符就绪。这个操作时间复杂度O(1)。即使文件描述符数目很多,效率也不会受到影响。没有数量限制:文件描述符数目无上限。

感谢各位大佬支持!!!

互三啦!!!


点击全文阅读


本文链接:http://zhangshiyu.com/post/185751.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1