目录
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 | 优先级带数据可写 | 是 | 是 |
POLLRDHUP | TCP连接被对方关闭,或者对方关闭了写操作。它由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)。即使文件描述符数目很多,效率也不会受到影响。没有数量限制:文件描述符数目无上限。
感谢各位大佬支持!!!
互三啦!!!