作者 | 神技圈子 责编 | 欧阳姝黎
出品 | CSDN博客
背景
网上讲 Epoll 的很多,但是都仅仅停留在简单的实例使用。但是在真正的工程应用中不可能就使用这种简单的过程式开发。那么 Epoll 到底该怎么用呢?下面我们就来好好讲讲。
Epoll 的优势
传统的处理网络 I/O 的多进程、多线程同步 I/O,或者是单线程的 select 和 poll 的事件驱动模型。其中多线程和多进程同步阻塞网络 I/O 技术,具有模型直观,使用方便等优点,但当处理高并发的网络连接时,因为存在 Fork(线程池可部分避免)和上下文切换操作产生较大的系统开销;同时内存开销也较大,不能满足服务器性能要求,适用于并发数不高以及服务器负载不大的场合。因此,为了提升系统的高并发情况下的性能和吞吐率,一般采用 IO 多路复用模型。IO 多路复用包括 Select,Poll 和 Epoll 三种方式,Epoll 作为 Linux 内核为处理大批文件描述符而改进的 poll。相对于 select 和 poll,Epoll 有以下两个优势:
支持理论上无限大的 socket 描述符
select限制了每个进程打开的socket描述符,例如Linux系统在linux/include/linux/posix_types.h中定义了_FD_SETSIZE为1024,如下图
即在 Linux 系统中 Select 最大只能支持 1024 个描述符,当需要监听 1024 个以上的描述符时,Select 函数就会监听出错。而 Epoll 使用红黑树管理注册的描述符,理论上能监听无限个描述符,现实中会收到内存的限制。
采用回调函数避免遍历所有描述符
select 和 poll 都是通过链表管理注册好的描述符,每次当有描述符监听到读写事件发生时,select 和 poll 都需要遍历整个链表从而找到有事件发生的描述符,在活动描述符较少的情况下,这种方案是非常低效的。Epoll 采用红黑树管理描述符,如果有描述符监听到读写时间发生,Epoll 会通过回调函数将该描述符插入到就绪队列中,即每次 Epoll 扫描的只是就绪队列中的发生读写事件的描述符。
设计思想
下面我们就来讲讲。
在 Epoll 中有个重要的结构体 epoll_event,它被用于注册所感兴趣的事件和回传所发生的事件。它的定义如下:
structepoll_event{ __uint32_tevents; /* epoll event / epoll_data_t data; / User data variable */ };
它当中的 epoll_data_t 保存了触发事件的某个文件描述符相关的数据。定义如下
typedefunionepoll_data { void*ptr; intfd; __uint32_tu32; __uint64_tu64; } epoll_data_t;
这里的关键设计是把 epoll_data_t 中的地址指针 ptr 同一个通信实体交互的通信单元类 Agent 进行绑定。为什么这么做呢?
我们把每个通信实体对应于一个 Agent 实例。当这个套接字或者文件描述符上有 I/O 事件到达时,Epoll 会返回这个套接字所绑定的地址指针,这里把这个地址指针指向这个套接字或者文件描述符对应的 Agent 实例,这样就可以返回 Agent 实例的地址,然后根据 I/O 事件的不同调用 Agent 里面对应的处理函数处理与通信实体间的交互。Agent 类处理的交互一般包括读写时间处理,以及 Agent 的启动和停止。
设计方法
主要的类有两个 Epoll 和 Agent:
Epoll 类封装事件驱动核心,负责事件通知。读写事件发生后,由 Agent 处理网络读写。
Agent 类是事件处理器的基类。TCPListenAgent 和 TCPAgent 继承自Agent,分别处理 TCP 监听套接字和普通 TCP 连接的网络收发。
Epoll 和 Agent 类关系图如下:
下面我们来介绍下这些类的设计
Agent 类
Agent 类声明如下:
classAgent { protected: intm_iConnect; //套接字连接状态 uint32_tm_ID; //Agent标识符,方便管理 public: Agent{} virtual~Agent{} virtualintSendData( void) = 0; virtualintRecvData( void) = 0; intgetStateconst; virtualintwirteback( boolresult) ; }; recvData 函数作为纯虚函数用于接受客户端的请求。成功返回读取的字节数,失败返回-1。 sendData 函数作为纯虚函数用于回复客户端的请求。成功返回写出去的字节数,失败返回-1。 TCPListenAgent 类
主要负责处理客户端发送过来的TCP连接请求。该类声明如下:
classTCPListenAgent: publicAgent { public: TCPListenAgent;~TCPListenAgent;boolinit( void) ; intSetNonblock( intfd) ; intRecvData; private: intm_listenFd; //监听套接字描述符structsockaddr_inm_cliAddr; //存储客户端连接的地址structsockadd_inm_servAddr; //存储服务端的地址26};
TCPListenAgent 类主要实现 Agent 类提供的两个纯虚函数,这里列出TCPListenAgent 类提供的主要方法:
init 函数用于初始化 TCPListenAgent 本身。初始化成功返回 true,失败返回 false。
recvData 函数用于处理客户端发送来的 TCP 连接请求。
Epoll 类
Epoll 类采用设计模式中的单例模式,对于整个程序,全局仅有唯一的一个 Epoll 实例。Epoll 类的声明如下
classEpoll{public: Epoll;~Epoll;boolepollInitial( intsize ) ; intdoEvent( intfd, Agent *agentPtr, intop, unsigned intevent) ; voidrun( void) ; staticEpoll* getInstance; intgetEpollFd( ) private: staticEpoll *m_epollObj ; //epoll的句柄intm_epfd; //epoll事件队列大小intm_eventsize; };
epollInitial 函数用于初始化 Epoll 对象,参数 size 是 Epoll 监听队列的长度。
doEvent 函数用于对 Epoll 事件进行操作,增加、删除或者修改。参数 agentPtr 具体的 Agent 对象,fd 为需要加入 Epoll 的描述符,op 为 Epoll的具体操作可传入参数包括 EPOLL_ADD,EPOLL_CTL,EPOLL_DEL,event为要监听的 Epoll 事件。
run 函数用于执行 EPOLL 整个运行流程。函数中主要调用 epol_wait 函数,当struct epoll_event的events判断是EPOLLIN时,Agent对象调用recvData函数,如果是 EPOLLOUT 事件时调用 sendData函数。
60+专家,13个技术领域,CSDN 《IT 人才成长路线图》重磅来袭!
直接扫码或微信搜索「CSDN」公众号,后台回复关键词「路线图」,即可获取完整路线图!
☞ 腾讯被深圳南山法院强制执行:执行标的25元;B站就招聘争议致歉;华为云回应是否将独立运作|极客头条 ☞ “你 100% 的时间都是 IBM 员工”,程序员的业余个人项目也属于公司? ☞ B站校招面试官“炫耀资产、贬低应试者”?当事人发长文回应,北邮学子要求向学校道歉
为了避免权属纠纷,特做如下说明:本站内容作品来自用户分享及互联网,仅供参考,无法核实真实出处,并不代表本网站赞同其观点和对其真实性负责,本网站仅提供信息存储空间服务,我们致力于保护作者版权,如果发现本站有涉嫌侵权的内容,欢迎发送邮件至youxuanhao@qq.com 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。