Linux-高级IO之poll、epoll
poll、epoll
- poll基本知识
- poll的优缺点
- epoll
- epoll工作原理
- epoll优点
- epoll工作方式
- 简单的epoll LT服务器
poll基本知识
前一篇:高级IO之select(点击直达)讲解了select,下面来看一下poll.
poll的机制和select,管理多个描述符也是进行轮询,根据描述符的状态进行处理,区别是poll没有最大文件描述符数量的限制。
poll函数接口
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数说明:
- fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合.
- nfds表示fds数组的长度.
- timeout表示poll函数的超时时间, 单位是毫秒(ms)
pollfd结构:
struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */
};
fd是哪个文件描述符,events:用户告诉内核你要帮我关心哪个文件描述符上的什么事件,revent:内核告诉用户,文件描述符上的事件已经就绪了。
events和revents的取值:
事件 | 描述 | 是否可作为输入 | 是否可作为输出 |
---|---|---|---|
POLLIN | 数据(包括普通数据和优先数据) | 是 | 是 |
POLLRDNORM | 普通数据可读 | 是 | 是 |
POLLRDBAND | 优先数据可读(Linux不支持) | 是 | 是 |
POLLPRI | 高优先级数据可读,比如TCP外带数据 | 是 | 是 |
POLLOUT | 数据(包括普通数据和优先数据) | 是 | 是 |
POLLWRNORM | 普通数据可写 | 是 | 是 |
POLLWRBAND | 优先级带数据可写 | 是 | 是 |
POLLRDHUP | TCP连接被对方关闭,或者对方关闭了写操作 | 是 | 是 |
POLLERR | 错误 | 否 | 是 |
POLLHUP | 挂起 | 否 | 是 |
POLLNVAL | 文件描述符没有打开 | 否 | 是 |
我们常用的就是读和写事件。
返回值
- 返回值小于0, 表示出错;
- 返回值等于0, 表示poll函数等待超时;
- 返回值大于0, 表示poll由于监听的文件描述符就绪而返回
poll的优缺点
poll优点
- pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比
select更方便. - poll并没有最大数量限制 (但是数量过大后性能也是会下降).
poll缺点
poll中监听的文件描述符数目增多时
- 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符.
- 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.
- 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降.
用poll简单监控标准输入
1 #include <poll.h> 2 #include <unistd.h>3 #include <iostream>4 using namespace std;5 int main() {6 struct pollfd poll_fd;7 poll_fd.fd = 0;8 poll_fd.events = POLLIN;9 10 for (;;) {11 int ret = poll(&poll_fd, 1, 1000);12 if (ret < 0) {13 cerr<<"poll"<<endl;14 continue;15 }16 if (ret == 0) {17 cout<<"poll timeout"<<endl;18 continue;19 }20 if (poll_fd.revents == POLLIN) {21 char buf[1024] = {0}; 22 read(0, buf, sizeof(buf) - 1);23 cout<<buf<<endl; 24 } 25 } 26 }
epoll
它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法.epoll总共有3个接口。
epoll的相关接口
int epoll_create(int size);
参数说明:
- 自从linux2.6.8之后,size参数是被忽略的. 用完之后, 必须调用close()关闭.
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数说明:
- 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
- 第一个参数是epoll_create()的返回值(epoll的句柄).
- 第二个参数表示动作,用三个宏来表示.
- 第三个参数是需要监听的fd.
- 第四个参数是告诉内核需要监听什么事
第二个参数
EPOLL_CTL_ADD
:注册新的fd到epfd中;EPOLL_CTL_MOD
:修改已经注册的fd的监听事件;EPOLL_CTL_DEL
:从epfd中删除一个fd
struct epoll_event结构:
用 vim /usr/include/sys/epoll.h 看epoll代码
79 typedef union epoll_data80 {81 void *ptr;82 int fd;83 uint32_t u32;84 uint64_t u64;85 } epoll_data_t;86 87 struct epoll_event88 {89 uint32_t events; /* Epoll events */90 epoll_data_t data; /* User data variable */91 } __EPOLL_PACKED;92
可以看到events是一些宏值
- EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
- EPOLLOUT : 表示对应的文件描述符可以写;
- EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
- EPOLLERR : 表示对应的文件描述符发生错误;
- EPOLLHUP : 表示对应的文件描述符被挂断;
- EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
- EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里
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表示函数失败
epoll工作原理
- 当进程调用epoll_create时,内核创建一个红黑树,红黑树的结点包含对应的事件,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)
- 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法,这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到就绪队列中
- 在epoll中,对于每一个事件,都会建立一个epitem结构体.
- epoll_ctrl将要监控的文件描述符进行注册;
- epoll_wait, 则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度
是O(1).只需检查就绪队列是否有结点。
epoll优点
- 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
- 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
- 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响.
- 没有数量限制: 文件描述符数目无上限
select、poll、epoll对比
epoll工作方式
epoll有2种工作方式-水平触发(LT)和边缘触发(ET)
水平触发Level Triggered 工作模式
epoll默认状态下就是LT工作模式.
- 当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分.
例:只读了1K数据, 缓冲区中还剩1K数据, 在第二次调用 epoll_wait 时, epoll_wait仍然会立刻返回并通知socket读事件就绪. - 直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回.
- 支持阻塞读写和非阻塞读写
边缘触发Edge Triggered工作模式
如果我们在第1步将socket添加到epoll描述符的时候使用了EPOLLET标志, epoll进入ET工作模式.
- 当epoll检测到socket上事件就绪时, 必须立刻处理
- 如上面的例子, 虽然只读了1K的数据, 缓冲区还剩1K的数据, 在第二次调用 epoll_wait 的时候, epoll_wait 不会再返回了
- 也就是说, ET模式下, 文件描述符上的事件就绪后, 只有一次处理机会
- ET的性能比LT性能更高( epoll_wait 返回的次数少了很多). Nginx默认采用ET模式使用epoll.
- 只支持非阻塞的读写
select和poll其实也是工作在LT模式下. epoll既可以支持LT, 也可以支持ET
在ET模式下必须要将fd设置位非阻塞
epoll的使用场景
epoll的高性能, 是有一定的特定场景的. 如果场景选择的不适宜, epoll的性能可能适得其反.对于多连接, 且多连接中只有一部分连接比较活跃时, 比较适合使用epoll.
简单的epoll LT服务器
#include "Sock.hpp"#define SIZE 64class bucket {public:char buffer[20]; //requestint pos;int fd;bucket(int sock) :fd(sock), pos(0){memset(buffer, 0, sizeof(buffer));}~bucket(){}};class EpollServer {private:int lsock;int port;int epfd;
public:EpollServer(int _p = 8081) :port(_p){}void AddEvent2Epoll(int sock, uint32_t event){struct epoll_event ev;ev.events = event;if (sock == lsock) {ev.data.ptr = nullptr;}else {ev.data.ptr = new bucket(sock);}epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ev);}void DelEventFromEpoll(int sock){close(sock);epoll_ctl(epfd, EPOLL_CTL_DEL, sock, nullptr);}void InitServer(){lsock = Sock::Socket();Sock::Setsockopt(lsock);Sock::Bind(lsock, port);Sock::Listen(lsock);epfd = epoll_create(256);if (epfd < 0) {cerr << "epoll_create error" << endl;exit(5);}cout << "listen sock: " << lsock << endl;cout << "epoll fd: " << epfd << endl;}void HandlerEvents(struct epoll_event revs[], int num){for (int i = 0; i < num; i++) {uint32_t ev = revs[i].events;if (ev & EPOLLIN) {if (revs[i].data.ptr != nullptr) {bucket* bp = (bucket*)revs[i].data.ptr;ssize_t s = recv(bp->fd, bp->buffer + bp->pos, sizeof(bp->buffer) - bp->pos, 0);if (s > 0) {bp->pos += s;cout << "client# " << bp->buffer << endl;if (bp->pos >= sizeof(bp->buffer)) {struct epoll_event temp;temp.events = EPOLLOUT;temp.data.ptr = bp;epoll_ctl(epfd, EPOLL_CTL_MOD, bp->fd, &temp);}}else if (s == 0){DelEventFromEpoll(bp->fd);delete bp;}else {}}else {int sock = Sock::Accept(lsock);if (sock > 0) {AddEvent2Epoll(sock, EPOLLIN);}}}else if (ev & EPOLLOUT) {bucket* bp = (bucket*)revs[i].data.ptr;send(bp->fd, bp->buffer, sizeof(bp->buffer), 0);DelEventFromEpoll(bp->fd);delete bp;}else {}}}void Start(){AddEvent2Epoll(lsock, EPOLLIN);int timeout = -1;struct epoll_event revs[SIZE];for (;;) {int num = epoll_wait(epfd, revs, SIZE, timeout);switch (num) {case 0:cout << "time out ..." << endl;break;case -1:cerr << "epoll_wait error" << endl;break;default:HandlerEvents(revs, num);break;}}}~EpollServer(){close(lsock);close(epfd);}
};
Linux-高级IO之poll、epoll相关推荐
- 五种高级IO | select poll epoll 水平触发模式 边缘触发模式 惊群问题
一.高级IO 在介绍多路复用IO之前,先介绍一下其它四种高级IO: 阻塞IO: 在内核将数据准备好之前,系统调用会一直等待.所以的套集字默认是阻塞方式. 非阻塞IO: 在内核还未将数据准备好,则系统调 ...
- linux网络编程--select/poll/epoll 详解
目录 参考链接 epoll函数 close epoll event EL/LT ET Edge Trigger 边沿触发工作模式 LT Level Trigger 水平触发工作模式 epoll 源码解 ...
- linux 高级IO函数之fcntl mmap/munmap
fcntl函数提供了对文件描述符的各种控制操作.另外一个常见的控制文件描述符属性和行为的系统调用是ioctl,而且ioctl比fcntl能够执行更多的控制.但是对于控制文件描述符常用的属性和行为,fc ...
- linux 高级IO函数之sendfile splice tee
sendfile函数在两个文件描述符之间传递数据(完全在内核中操作),从而避免内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝.函数的定义如下: #include<sys/send ...
- epoll编程实例客户端_深入底层探析网络编程之多路复用器(select,poll,epoll)
IO模型 只关注IO,不关注IO读写完成后的事情. 同步:程序(APP)自己进行读/写操作 异步:由Kernel完成读/写,程序跑起来感觉像没有访问IO,访问的是buffer 阻塞:BLOCKING, ...
- epoll原理剖析以及reactor模型应用丨网络编程|网络IO|select|poll|socket|reactor多核实现丨c/c++linux服务器开发
epoll原理剖析以及reactor模型应用 视频讲解如下,点击观看: epoll原理剖析以及reactor模型应用丨网络编程|网络IO|select|poll|socket|reactor多核实现丨 ...
- Linux的IO模型 —— 多路复用(select、poll、epoll)
目录 1.前言 2.内核空间.用户空间.同步.异步.阻塞.非阻塞 3.同步阻塞 IO 4.同步非阻塞IO 5.多路复用 5.1 select 5.2 poll 5.3 epoll 1.前言 应用进 ...
- Linux之高级IO
目录 一.五种IO模型 以网络为例 什么叫做IO? 什么叫做高效的IO呢? 为什么第二个大爷的效率很高呢? 五种IO模型 感性认识 这五个人,在钓鱼的时候,谁的效率是最高的? 阻塞IO 非阻塞IO 信 ...
- 【Linux练习生】高级IO
本文收录于专栏:Linux 关注作者,持续阅读作者的文章,学习更多知识! https://blog.csdn.net/weixin_53306029?spm=1001.2014.3001.5343 高 ...
最新文章
- 不知道这些AI术语,还敢说你很了解AI吗?
- C语言并发执行的进程怎么写,多进程并发写文件 多进程并发售票 用c语言写
- 服务中没有listen_Go语言微服务框架实战:2.Go语言实现RPC编程绍
- CTFshow 文件上传 web155
- swift语言新特性:可选值
- 452. 用最少数量的箭引爆气球(贪心算法+思路+详解)07
- 基于单片机步进电机ppt答辩_基于MCU和DSP的步进电机控制技术分析
- nginx哪个版本性能好_nginx性能为什么好
- 关于设计模式的感悟2
- vs code使用问题
- java三年技术差_3年经验Java程序员面阿里P6 差距在哪里
- Dart基础-运算符
- 百度大脑EasyMonitor升级2.0,全新发布软硬一体方案EM-BOX 边缘部署易集成
- OpenStack DVR 原理深入分析
- 推荐6款神器软件,你用过哪些?
- 效率源linux,FLOOPY效率源硬盘坏道修复工具 修复坏硬盘的时候 为何只能手动修复 而自动修复又说找不到硬盘?...
- Jsbeautifier JS代码美化库
- 谈谈大数据架构下的存储系统
- 使用网口转换器更换网线后无法上网
- win7旗舰版安装mysql5.7.23