无锡专业做网站的公司,那些做网站的那些软件都叫啥,贵阳网站开发招聘,wordpress 评论翻页文章目录 Reactor模型的典型分类单Reactor单线程单Reactor多线程多Reactor多线程本项目中实现的主从Reactor One Thread One Loop各模型的优点与缺点 项目分解Reactor服务器模块BufferSocketChannelEpollerTimerWheelEventLoopAnyConnectionAcceptorLoopThreadLoopThreadPoolTc… 文章目录 Reactor模型的典型分类单Reactor单线程单Reactor多线程多Reactor多线程本项目中实现的主从Reactor One Thread One Loop各模型的优点与缺点 项目分解Reactor服务器模块BufferSocketChannelEpollerTimerWheelEventLoopAnyConnectionAcceptorLoopThreadLoopThreadPoolTcpServer HTTP服务器模块UtilRequest和ResponseResponseContextHttpServer 本篇博客是对自己实现的主从Reactor高并发服务器的总结。 Reactor模型的典型分类
单Reactor单线程 单Reactor多线程 多Reactor多线程 本项目中实现的主从Reactor One Thread One Loop 各模型的优点与缺点
单Reactor单线程
优点实现简单不涉及到进程/线程间通信以及资源争抢缺点由于所有操作均在单线程中串行执行一旦有任务处理较慢或者请求较多时容易导致后面的任务处理或者请求得不到响应。并且由于是单线程没有充分利用好CPU多核资源最终非常容易达到性能瓶颈。
单Reactor多线程
优点利用了CPU多核资源缺点单个Reactor线程不仅处理了新建连接请求而且还处理了数据通信请求也就是管理了所有的fd上的一切事件在高并发场景下也非常容易达到性能评价。
多Reactor多线程
优点充分利用了CPU多核资源主Reactor只负责获取连接副Reactor负责已获取的连接各司其职解决了前面两种模型的性能问题缺点实现复杂。
主从Reactor One Thread One Loop
由于也采用了主从Reactor模式所以性能不差但为了服务器的实现更简单放弃了线程池的实现。
项目分解
本项目共分为两大模块Reactor服务器模块和基于Reactor服务器模块实现的HTTP服务器模块。
下面的项目分解只是简单的说明了一下各模块的功能项目源码中有详细的注释讲解所以强烈建议搭配项目源码一起食用。
Reactor服务器模块
服务器模块共有以下子模块 Buffer
recv并不能够保证读取到一个完整的协议数据所以必须要将读取到的数据先暂存起来然后上层检查数据完整性若完整则拿走数据不完整则一直等读取到一个完整的协议数据时再拿走数据那么这时就需要一个缓冲区能暂时存放recv读取到的数据。并且写入数据时也不能直接调用send因为fd是要被epoll监控的但用户又不知道什么时候调用所以用户可以直接将数据写入缓冲区中当fd上的写事件触发时会自动将缓冲区中的数据send到fd中。
本模块就实现的是这样的一个缓冲区。
缓冲区结构如下 Socket
封装系统调用socket使对于socket的各项操作更加方便。
Channel
Channel模块是对一个fd进行事件监控管理以及事件回调管理的
功能大概有 开启/关闭fd的事件监控读、写 关闭fd的所有事件监控 判断fd的事件监控是否被开启了 设置事件触发后的回调函数读事件、写事件、错误事件、关闭事件、任意事件 调用已经触发的事件回调函数。
但要注意关于fd的开启/关闭事件监控并不是真正在Channel模块执行的而是在Epoller模块执行的。Channel模块只是将fd的相关监控操作和相关事件回调整合在了一起。
Epoller
Epoller模块是对epoll系列操作进行的封装让对fd的事件监控操作更加简单。
通过传入一个Channel指针获取到fd需要监控的事件然后Epoller模块就把这些事件进行监控而当有事件触发时Epoller模块就把已经触发的事件通过Channel传出再由Channel内部调用事件回调。
功能大概有
添加/更新事件监控移除事件监控开始事件监控。
TimerWheel
TimerWheel是一个定时任务管理模块。
大致思想就是将任务封装到TimerTask的析构函数中然后用shared_ptr管理起来放入TimerWheel中的vector里每隔一秒就清空一下vector里的元素此时调用析构函数就会调用定时任务了。 每隔一秒step_就前进一步step_走到哪里就清空哪里然后当最后一个shared_ptr调用析构函数时就会调用定时任务。
step_的每秒移动是根据timerfd技术来实现的。
创建一个timerfd让内核每隔一秒写入一次然后用Channel管理timerfd注册一个读事件在读事件里step_这样内核每隔一秒写入一次就触发一次读事件就会step_一次。
EventLoop
EventLoop模块就是副Reactor模块封装了Epoller模块和TimerWheel模块并且一个EventLoop就是一个线程。
大致功能有
更新/移除事件监控调用Epoller接口添加/刷新/取消/移除定时任务添加任务到任务队列中启动事件监控调用Epoller接口调用事件回调调用Channel接口执行任务队列中的任务。
关于任务队列要详细说一下
对于一个连接用户所有关于连接的操作都是线程不安全的比如在某个事件回调执行过程中用户开辟了一个线程池这个线程池都是共享这个连接的那么假设有若干个线程同时对定时任务进行操作就会出现线程安全问题。所以用户所有的对于连接的操作都是非线程安全的但是又不能给每个连接的接口都添加锁这样效率就太低了。于是就有了一个解决办法在EventLoop模块里创建一个任务队列所有的连接的接口在调用时都进行一下判断接口内部判断若是副Reactor线程就直接执行接口若是其它线程就将该任务压入队列中由副Reactor线程统一执行。这样就避免了多线程对于连接访问的线程安全问题。
上面功能的第四点是在同一函数中执行的那么就会出现一种情况任务队列中有任务了但此时没有事件触发epoll_wait被阻塞最终导致任务队列中的任务得不到及时执行。所以这里用了eventfd技术解决。eventfd用Channel管理起来注册一个读事件然后在将任务添加到任务队列后往eventfd里写入数据此时就会触发读事件epoll_wait不会被阻塞任务队列中的任务也就能够被及时执行了。
Any
Any模块是模仿C17中的any类实现的。
TCP服务器并不知道上层要运用什么协议也就无法用一个特定类型保存上层的上下文信息所以用一个Any类来保存上层的上下文信息。
实现思路
要实现一个类能够存放任意类型的数据那么该类必定不能是模版类模版类不能自动推演类型并且模版类在实例化之后就只能存放单一类型的数据了。但是函数模版可以自动推演类型于是就想到将类的构造函数设置成模版函数成员变量为void *指针但是void *太不安全了。于是又想到在Any类的内部创建一对父子类子类是模版类成员变量为父类指针在Any的构造函数中new一个子类对象用父类指针管理就能够实现简易的Any类。
Connection
Connection模块是子模块中最复杂的模块是对Buffer、Socket、Channel、Any、模块的整合还关联了EventLoop模块。
大致功能就是
设置任务回调函数连接创建成功的回调消息到来的回调任意事件回调 . . . . . .发送/读取数据开启/关闭非活跃连接销毁关闭连接切换协议。
Connection模块所有的对外提供的接口在调用时都要判断是否和副Reactor线程是同一个线程是则直接执行不是则压入队列。但是对于关闭连接的操作无需进行判断应该直接压入队列关闭连接必须要在所有的事件触发函数执行完之后在队列中执行。
假设有一种场景非活跃连接销毁时间是10s1、2、3、4、5号都有事件触发1号事件执行了20s那么timerfd就超时了20次假设2、3、4中有一个就是timerfd事件然后指针走了20下再然后后面还没来得及执行的事件的连接就被销毁了此时再去执行触发事件就会发生错误。所以关闭连接的操作必须要在触发事件全部调用完之后在任务队列中执行。
Acceptor
Acceptor模块也就是主Reactor模块负责获取新连接内部有一个EventLoop和一个Channel来管理监听套接字。
LoopThread
该模块将EventLoop和线程强绑定在了一起。为什么非要这么做呢
因为EventLoop模块在初始化的时候获取当前线程ID那么用户可能在一个线程内部创建好几个EventLoop然后再将这几个EventLoop分配给其它线程这时虽然一个EventLoop占一个线程但此时EventLoop内部的线程ID和实际所处的线程ID是不一样的。
LoopThreadPool
将LoopThread模块封装成一个线程池更加方便了服务器对于LoopThread数量的掌控。
TcpServer
是对所有模块的整合但主要的成员也就是一个主Reactor一个EventLoop和一个Acceptor、一个LoopThreadPool。
主要功能有
设置任务回调函数连接创建成功的回调消息到来的回调任意事件回调 . . . . . .设置LoopThreadPool的线程数量开启非活跃连接销毁添加定时任务。
HTTP服务器模块 Util
该模块提供了一些工具函数比如字符串分割函数、读文件、写文件、编码、解码等。
Request和Response
该模块存放了解析后的Http请求报文数据并且还提供了一些方法能够快速获取Request数据。
Response
该模块存放了解析后的Http响应报文数据并且还提供了一些方法能够快速获取Response数据。
Context
该模块是接收Request的上下文模块服务端接收到的数据有可能并不是一个一条完整的Http报文所以需要该模块来记录下接收Http报文的过程上下文。
HttpServer
对上面所有模块的整合并且设置了不同的Http请求与回调方法的映射。