当前位置: 首页 > article >正文

基于epoll的Reactor模型

一、代码展示

1、主函数 main.cc(第一级别)

先控制台获取服务器的端口号,绑定端口号IP地址。PackageParse作为报文解析并发送接收报文的中间类,Listener是服务器的监听套接字,HandlerConnection是连接套接字,用于和业务结合,所以绑定方法PackageParse::Excute,Reactor是事件派发器,里面会有两种套接字的读写事件初始化和循环检测epoll读写事件就绪并处理。初始化完成就要开始绑定两种套接字的读写方法。监听套接字只要关心读方法(有新连接到来就是监听套接字的读事件就绪)普通套接字需要有读写事件和处理异常事件方法。最后先添加监听套接字,开始任务派发。

2、套接字封装 Connection.hpp (第二级别)

从现在开始只有连接的概念,没有单独一个套接字的概念,也是为例方便管理,引入连接类。一个套接字我们给他配备了单独的两个缓冲区(输入输出缓冲区)_outbufffer, _inbuffer,每一个套接字对应的处理读写事件和异常的方法_handler_recver, _handler_sender, _handler_excepter, 还有回指向Reactor对象的指针(让两者相互指向)_R,套接字的类型_type,套接字的关心事件_events,最后是连接套接字的客户端信息_addr

类Connection的工作就是处理好一个连接的信息,不考虑管理所有连接。包括设置三个方法,关心事件,回指指针,连接属性,绑定信息和处理缓冲区,添加输入输出缓冲区,配合_handler_sender删除输出缓冲区内容。

3、连接管理事件派发容器 Reactor.hpp (第二级别)

作为最底层直接与epoll打交道的容器,需要利用好Connection连接类来管理一个个的套接字。还有一个任务就是派发任务给一个个的连接。

任务一:管理套接字,用到了哈希表_connections,一个sockfd对应一个Connection对象,四个方法(是上层自己另写的,对应两种套接字不同的事件处理方法,之后展示)用于初始化并管理连接。

函数AddConnection给我传文件描述符,事件,客户端信息和连接类型,我来帮你用epoll管理连接。先设置Connection对象属性,然后把特定文件描述符所关心的特定事件交给epoll,最后添加给哈希表_connections

函数EnableConnectionReadWrite开启一个连接对读写事件的关心,把特定文件描述符所关心的特定事件交给epoll

函数DelConnection删除一个连接,先在内核移除sockfd关心(因为epoll要求文件描述符要正常),再一旦出异常sockfd要关闭,最后移除_connections

任务二:事件派发,用到了封装的epoll类和读取就绪事件的revents事件缓冲区。

函数LoopOnce是一次事件派发逻辑,一开始等待事件就绪,读到revents里面就绪的事件后循环处理就绪事件。先拿到文件描述符和对应的就绪事件,然后读事件就续就调用对应文件描述符对应连接里面的接收函数,写事件就续就调用对应文件描述符对应连接里面的发送函数。最后为了统一处理异常,如果出错依然改成读写事件就绪,再读写事件里面统一处理异常。

函数Dispatcher就是main.cc里面调用的函数,进行循环调用LoopOnce即可完成事件派发。

4、管理epoll类 Epoller.hpp (第二级别)

用于管理创建好的epfd,由于管理IO的方式有很多,有select, poll, epoll等,所以用一个基类Multiplex表示管理IO的类,子类封装了epoll方式。主要工作:添加,修改,等待,删除特定文件描述符的特定事件。

在构造函数时就要创建epfd,然后就是用原生接口实现上述四种功能。

5、监听套接字类 Listener.hpp (第三级别)

主要工作:构造函数初始化监听套接字,实现监听套接字的获取新连接功能。

函数Accepter在ET模式下要循环获取连接的文件描述符,新连接要调用AddConnection托管给Reactor

6、普通套接字类 HandlerConnection.hpp (第三级别)

普通的连接套接字已经与业务相结合了,所以有一个字段_process是业务函数。

主要工作是处理读事件,写事件和异常事件。

补充知识:多路转接如何正确处理写事件?

多进程和多线程的写入更简单,可以阻塞式写入。

在多路转接中,读事件就绪意味着输入缓冲区有数据或新连接到来,写事件就绪意味着不关心数据,关心发送缓冲区中有无空间写入,有空间就是就绪。

把一个sockfd托管给select, poll, epoll代表当前sockfd事件未就绪。

一个新的sockfd输入输出缓冲区默认为空。

从上述客观事实得出两个结论:

(1)sockfd新建的情况下,默认读事件不就绪,注意ET模式下每次还要读完输入缓冲区,所以正常情况下输入缓冲区一直为空,读事件一直不就绪,所以要常开启读事件关心(对应到函数HandlerRecver就是结束时不要关闭读事件的关心)。

(2)sockfd新建的情况下,默认写事件就绪。所以一开始不关心EPOLLOUT事件,只有当输出缓冲区满了&&还有数据要写入,才开启写事件关心,之后剩余的数据回epoll自动发送(当写事件就绪了LoopOnce自己会调用_handler_sender函数)

细节:

(1)如果一开始直接设EPOLLOUT,epoll就会大量就绪。

(2)未来发完了数据,一定要关闭EPOLLOUT关心。

(3)如果缓冲区没满,数据也发完了,不用开启EPOLLOUT关心。

(4)如果设置EPOLLOUT,epoll对EPOLLOUT的首次关心回默认就绪一次。

7、服务器与客户端报文IO的中间人 PackageParse.hpp (第四级别)

有一个业务字段用于调用业务处理函数。

主要工作:从输入缓冲区中读取客户端的请求数据,再反序列化得到请求对象,通过最上层的业务函数得到应答对象,再序列化应答对象,构建应答报文之后再发送回输出缓冲区,最后激活写事件关心发送应答报文。

8、一个简单的网络计算器业务 NetCal.hpp (第四级别)

主要工作:接收请求对象,返回应答对象。

9、自定义tcp协议 Protocol.hpp (第四级别)

主要工作:供PackageParse.hpp和NetCal.hpp通信使用,最上层网络通信一定要有协议支持。

首先要有添加报头和解析完整报文两个全局函数,用于报文的封装和解析。然后商量请求报文格式和应答报文格式,所以设计请求类和应答类,里面都会有序列化和反序列化。序列化就是把类里面的对象数据变成报文,反序列化就是把报文里面的数据提取成对象数据储存。

最后可以封装两个创建对象的工厂,上层只能用工厂创建请求应答对象。

10、三个辅助类 Socket.hpp inetAddr.hpp log.hpp

二、整体思路


http://www.kler.cn/news/336183.html

相关文章:

  • 面试系列-淘天提前批面试
  • 活体检测标签之2.4G有源RFID--SI24R2F+
  • 儿童需要学习C++多久才能参加信息学奥赛的CSP-J比赛?
  • 探索未来:揭秘pymqtt,AI与物联网的新桥梁
  • 【java】数据类型与变量以及操作符
  • 关于Excel将列号由字母改为数字
  • 微信小程序开发-调试及配置文件介绍
  • Android车载——VehicleHal运行流程(Android 11)
  • 开源跨平台三维模型轻量化软件osgGISPlugins-1、简介
  • [MarsCode 系列] 查找热点数据
  • 60 序列到序列学习(seq2seq)_by《李沐:动手学深度学习v2》pytorch版
  • 通信工程学习:什么是NFS网络文件系统
  • 数据库概述(3)
  • 数据结构之树(2)
  • CART决策树特征重复使用问题:构建CART决策树时,使用了特征a作为分裂点,其子树仍然可能再次使用特征a作为分裂点
  • Python数据分析和可视化
  • 【Mac】和【安卓手机】 通过有线方式实现投屏
  • Vivado - JTAG to AXI Master (GPIO、HLS_IP、UART、IIC)
  • CSS——文字打字机效果
  • 多维放缩(MDS)与主成分分析(PCA)