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相关推荐

  1. 五种高级IO | select poll epoll 水平触发模式 边缘触发模式 惊群问题

    一.高级IO 在介绍多路复用IO之前,先介绍一下其它四种高级IO: 阻塞IO: 在内核将数据准备好之前,系统调用会一直等待.所以的套集字默认是阻塞方式. 非阻塞IO: 在内核还未将数据准备好,则系统调 ...

  2. linux网络编程--select/poll/epoll 详解

    目录 参考链接 epoll函数 close epoll event EL/LT ET Edge Trigger 边沿触发工作模式 LT Level Trigger 水平触发工作模式 epoll 源码解 ...

  3. linux 高级IO函数之fcntl mmap/munmap

    fcntl函数提供了对文件描述符的各种控制操作.另外一个常见的控制文件描述符属性和行为的系统调用是ioctl,而且ioctl比fcntl能够执行更多的控制.但是对于控制文件描述符常用的属性和行为,fc ...

  4. linux 高级IO函数之sendfile splice tee

    sendfile函数在两个文件描述符之间传递数据(完全在内核中操作),从而避免内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝.函数的定义如下: #include<sys/send ...

  5. epoll编程实例客户端_深入底层探析网络编程之多路复用器(select,poll,epoll)

    IO模型 只关注IO,不关注IO读写完成后的事情. 同步:程序(APP)自己进行读/写操作 异步:由Kernel完成读/写,程序跑起来感觉像没有访问IO,访问的是buffer 阻塞:BLOCKING, ...

  6. epoll原理剖析以及reactor模型应用丨网络编程|网络IO|select|poll|socket|reactor多核实现丨c/c++linux服务器开发

    epoll原理剖析以及reactor模型应用 视频讲解如下,点击观看: epoll原理剖析以及reactor模型应用丨网络编程|网络IO|select|poll|socket|reactor多核实现丨 ...

  7. Linux的IO模型 —— 多路复用(select、poll、epoll)

    目录 1.前言 2.内核空间.用户空间.同步.异步.阻塞.非阻塞 3.同步阻塞 IO 4.同步非阻塞IO 5.多路复用 5.1 select 5.2 poll 5.3 epoll 1.前言   应用进 ...

  8. Linux之高级IO

    目录 一.五种IO模型 以网络为例 什么叫做IO? 什么叫做高效的IO呢? 为什么第二个大爷的效率很高呢? 五种IO模型 感性认识 这五个人,在钓鱼的时候,谁的效率是最高的? 阻塞IO 非阻塞IO 信 ...

  9. 【Linux练习生】高级IO

    本文收录于专栏:Linux 关注作者,持续阅读作者的文章,学习更多知识! https://blog.csdn.net/weixin_53306029?spm=1001.2014.3001.5343 高 ...

最新文章

  1. 不知道这些AI术语,还敢说你很了解AI吗?
  2. C语言并发执行的进程怎么写,多进程并发写文件 多进程并发售票 用c语言写
  3. 服务中没有listen_Go语言微服务框架实战:2.Go语言实现RPC编程绍
  4. CTFshow 文件上传 web155
  5. swift语言新特性:可选值
  6. 452. 用最少数量的箭引爆气球(贪心算法+思路+详解)07
  7. 基于单片机步进电机ppt答辩_基于MCU和DSP的步进电机控制技术分析
  8. nginx哪个版本性能好_nginx性能为什么好
  9. 关于设计模式的感悟2
  10. vs code使用问题
  11. java三年技术差_3年经验Java程序员面阿里P6 差距在哪里
  12. Dart基础-运算符
  13. 百度大脑EasyMonitor升级2.0,全新发布软硬一体方案EM-BOX 边缘部署易集成
  14. OpenStack DVR 原理深入分析
  15. 推荐6款神器软件,你用过哪些?
  16. 效率源linux,FLOOPY效率源硬盘坏道修复工具 修复坏硬盘的时候 为何只能手动修复 而自动修复又说找不到硬盘?...
  17. Jsbeautifier JS代码美化库
  18. 谈谈大数据架构下的存储系统
  19. 使用网口转换器更换网线后无法上网
  20. win7旗舰版安装mysql5.7.23

热门文章

  1. PostgreSQL数据库安装教程
  2. 【数据结构】Java实现双向链表
  3. chrome浏览器安装SeleniumWebDriver
  4. 500台计算机分配方案,局域网超过500台电脑如何分配ip?有无假如方案?
  5. 在全志的山寨平板上跑起linux
  6. MIL-100(Cr),cas840523-88-8
  7. 今天实在忍不住,把极速星空的密码给破了
  8. 从文本挖掘来解读许巍
  9. Java基础知识——Stream
  10. 一致性(Consistency)介绍