:前置知识与功能目标)
系列文章目录文章目录系列文章目录前言一、项目认识二、知识背景2.1 HTTP服务器2.2 Reactor模型三、选择的原因3.1 单 Reactor 单线程单 I/O 多路复用 业务处理3.2 单 Reactor 多线程单 I/O 多路复用 线程池业务处理3.3 多 Reactor 多线程多 I/O 多路复用 线程池业务处理四、项目解析4.1 SERVER模块4.2 HTTP协议模块总结前言大家好我是曦夜日长。经过这么长时间的学习也是修得正果正式来到我们的标准项目栏目正如专栏所示本系列实现仿muduo库OneThreadOneLoop式主从Reactor模型实现⾼并发服务器从零到有的开发。请跟着我的脚步开始我们第一个项目的开发。一、项目认识我们要开发的项目服务器可以说是最经典的服务器模板这里给出百度资料主从 Reactor OneThreadOneLoop 模型至今仍广泛用于工业级高性能服务C 生态 muduo、brpcJava 生态 Netty 均采用同类架构被应用在即时通讯 IM、游戏网关、流媒体服务器、IoT 设备网关、RPC 网关等海量长连接场景。当然我们开发的只是服务器底层其中内部业务的实现还需要我们自己部署作为主从Reactor它有着这样的特性主Reactor获取到新连接后分发给⼦Reactor进⾏通信事件监控。而子Reactor线程监控各⾃的描述符的读写事件进⾏数据读写以及业务处理。在谈及这样的Reactor模型One Thread OneLoop常被重点标注其的思想就是把所有的操作都放到⼀个线程中进行⼀个线程对应⼀个事件处理的循环。借上面的图我们来分析一下具体的流程客户端发起 TCP 连接请求多个客户端向服务器监听端口发起连接。主 ReactormainReactor只负责监听 accept 事件mainReactor 内部 epoll 监听 listenfd捕获新连接事件调用accept()拿到新客户端 socket 的 fd完成三次握手得到 TcpConnection。新连接分发mainReactor 通过负载均衡策略muduo 是轮询将这个新连接 fd投递到其中一个从属 Reactor (subReactor)。从属 Reactor 接管连接注册 IO 事件被选中的 subReactor一个线程对应一个 EventLoopOneThreadOneLoop把这个新 fd 注册进自己内部的 epoll后续这个连接永久绑定该 subReactor不会切换线程。自此mainReactor 不再管这条连接只继续等待下一个新客户端连接。从属 Reactor 循环监控 IO 事件subReactor 的 epoll 持续等待事件客户端发数据 → socket可读事件触发内核缓冲区可写入数据 → socket可写事件触发连接断开 → 关闭事件。事件回调处理subReactor 线程检测到可读事件在当前 IO 线程内执行读操作从 socket 读取数据直接在这个 IO 线程调用onMessage业务回调解析数据包、执行业务逻辑业务处理完成后如果需要回复客户端在当前线程执行 send 写数据。连接关闭当检测到连接关闭事件subReactor 在本线程清理该 TcpConnection 资源从 epoll 移除 fd。二、知识背景我们想实现这样一个项目主要依靠的是我们之前学习的HTTP服务器和Reactor事件派发器现在爆发鬼脑回想一下2.1 HTTP服务器HTTPHyperTextTransferProtocol超⽂本传输协议是应⽤层协议是⼀种简单的请求-响应协议但我们要明白的是它只是服务于应用层的数据传输方式根本还是在于TCP为基础搭建的服务器上。因此可以说HTTP服务器本质就是个TCP服务器所以就需要socket-bind-listen-accept这几个我们熟知的流程在内部对数据HTTP协议封装、调用业务逻辑当然在当前项目中的服务器本⾝并不存在业务。2.2 Reactor模型Reactor 模式是指通过⼀个或多个输⼊同时传递给服务器进⾏请求处理时的事件驱动处理模式服务端程序处理传⼊多路请求并将它们同步分派给请求对应的处理线程。又名Dispatcher 模式基础的Reactor模型的思路与基础的TcpServer的阻塞式IO不同采用多路复用的方法这两者的区别就是一直等和等多个比如钓鱼张三只带了一个鱼竿钓鱼时只能等着鱼上钩而多路复用则是李四带了多个鱼竿等着只要有上钩就去处理。但这两种方法本质区别是等的区别处理事件过程是相同的。Reactor基本思想是监控fd如果fd就绪就告诉内核进而响应到用户应用层处理后重新等待。这里就不讲述过多了详细内容可以查看有关的博客我的没写:(三、选择的原因那就有人要疑惑了我之前不是实现了一个Reactor服务器吗那为什么还有跟着你的项目写一个我写过的东西。其实不然我们之前实现的只是一个单Reactor 多线程的模式它有着这样的一种缺陷同一个连接 fd 上先后触发的 IO 事件会被封装为独立任务直接投递至全局线程池线程池不保证任务完成顺序与事件入队顺序一致若后提交的请求任务执行更快会优先返回响应造成同一连接上请求与响应顺序错乱引发协议解析异常。因此我们不可能使用这样一种具有严重处理缺陷的代码作为能够胜任高并发服务器开发的基础实现。那让我们认识一下Reactor的种类3.1 单 Reactor 单线程单 I/O 多路复用 业务处理这是最朴素的一种实现整个服务器就一个线程既负责监听事件又负责干活。工作流程通过 I/O 多路复用模型进行客户端请求监控。触发事件后进行事件处理a. 如果是新建连接请求则获取新建连接并添加至多路复用模型进行事件监控。b. 如果是数据通信请求则进行对应的数据处理接收数据、处理数据、发送响应。优点所有操作均在同一线程中完成思想流程较为简单不涉及进程/线程间通信及资源争抢问题。缺点无法有效利用 CPU 多核资源很容易达到性能瓶颈。适用场景适用于客户端数量较少、且处理速度较为快速的场景。处理较慢或活跃连接较多时会导致串行处理后处理的连接长时间无法得到响应。可以看到它的简单是靠牺牲并发能力换来的——一旦某个业务处理耗时变长整个服务器都会被它拖住因为它根本没有第二个线程能接手。3.2 单 Reactor 多线程单 I/O 多路复用 线程池业务处理既然单线程扛不住最直接的想法就是把监听和干活拆开干活交给线程池。这就是单 Reactor 多线程模式。工作流程Reactor 线程通过 I/O 多路复用模型进行客户端请求监控。触发事件后进行事件处理a. 如果是新建连接请求则获取新建连接并添加至多路复用模型进行事件监控。b. 如果是数据通信请求则接收数据后分发给 Worker 线程池进行业务处理。c. 工作线程处理完毕后将响应交给 Reactor 线程进行数据响应。优点充分利用 CPU 多核资源。缺点多线程间的数据共享访问控制较为复杂且单个 Reactor 承担所有事件的监听和响应在单线程中运行高并发场景下容易成为性能瓶颈。注意第 2.c 步这个细节——响应最终还是要回到 Reactor 线程去发送。这既带来了各线程都要和 Reactor 打交道的同步成本也正是我们在上一节踩过的坑所在业务任务一旦并发投递进线程池同一连接上请求的处理与响应的先后顺序就没人保证了顺序错乱、协议解析异常随之而来。3.3 多 Reactor 多线程多 I/O 多路复用 线程池业务处理单 Reactor 的瓶颈在于所有事件的监听和响应都压在同一个线程上。那思路就很清楚了把 Reactor 本身也拆开让它多干活。这就是多 Reactor 多线程模式也就是常说的主从 ReactorMain-Sub Reactor。工作流程在主 Reactor中处理新连接请求事件有新连接到来则分发到子 Reactor中监控。在子 Reactor中进行客户端通信监控有事件触发则接收数据分发给Worker 线程池。Worker 线程池分配独立的线程进行具体的业务处理。a. 工作线程处理完毕后将响应交给子 Reactor线程进行数据响应。优点充分利用 CPU 多核资源主从 Reactor 各司其职。缺点实现复杂度最高——连接如何分发、线程间如何通信、生命周期如何管理都需要自己设计好代码量和对细节的把握要求也远高于前两种。与前两种模式相比它最大的变化是主 Reactor 只负责接客子 Reactor 负责服务。新连接被主 Reactor 接进来后会被分配给某一个子 Reactor此后这条连接上的所有 I/O 事件都由这个子 Reactor 处理。回到开头那个问题为什么我们要在已经有一个单 Reactor 多线程的服务器之后再写一个主从 Reactor答案就在 3.3 的分工结构里——同一个连接 fd 上的事件从此由同一个子 Reactor 线程顺序处理。既不会出现后提交的任务先返回的乱序问题又因为子 Reactor 可以有多个把 CPU 多核真正用了起来同时主 Reactor 只做分发、不做业务保证它不会被慢业务拖住能持续稳定地接收新连接。所以主从 Reactor 不是更花哨的写法而是为了解决单 Reactor 线程池在连接级顺序性和高并发扩展性上的双重缺陷——这也正是我们选择它的原因。四、项目解析基于以上的理解我们要实现的是⼀个带有协议⽀持的Reactor模型⾼性能服务器因此将整个项⽬的实现划分为两个⼤的模块SERVER模块实现Reactor模型的TCP服务器协议模块对当前的Reactor模型服务器提供应⽤层协议⽀持4.1 SERVER模块SERVER模块就是对所有的连接以及线程进⾏管理让它们各司其职在合适的时候做合适的事最终完成⾼性能服务器组件的实现。并且这些管理可以详细分为监听连接管理对监听连接进⾏管理。通信连接管理对通信连接进⾏管理。超时连接管理对超时连接进⾏管理。而这个大模块就可以分成多个小模块组装实现Buff模块缓冲区模块⽤于实现通信中⽤⼾态的接收缓冲区和发送缓冲区功能Socket模块负责服务器的系列操作Channel模块对每个连接进行监控的模块实现对描述符可读可写错误…事件的管理操作以及Poller模块对描述符进⾏IO事件监控就绪后根据不同的事件回调不同的处理函数功能Poller模块是对epoll模型的封装实现对事件的添加、修改和删除等功能Connection模块是对Buff、Socket、Channel模块的封装使得可以管理所有的连接Acceptor模块Acceptor模块是对Socket模块Channel模块的⼀个整体封装实现了对⼀个监听套接字的整体的管理TimerQueue模块是实现固定时间定时任务的模块可以理解就是要给定时任务管理器向定时任务管理器中添加⼀个任务任务将在固定时间后被执⾏同时也可以通过刷新定时任务来延迟任务的执⾏EventLoop模块可以理解就是我们上边所说的Reactor模块它是对Poller模块TimerQueue模块Socket模块的⼀个整体封装进⾏所有描述符的事件监控TcpServer模块这个模块是⼀个整体Tcp服务器模块的封装内部封装了Acceptor模块EventLoopThreadPool模块4.2 HTTP协议模块HTTP协议模块⽤于对⾼并发服务器模块进⾏协议⽀持基于提供的协议⽀持能够更⽅便的完成指定协议服务器的搭建。⽽HTTP协议⽀持模块的实现可以细分为以下⼏个模块。Util模块这个模块是⼀个⼯具模块主要提供HTTP协议模块所⽤到的⼀些⼯具函数⽐如url编解码⽂件读写…等。HttpRequest模块这个模块是HTTP请求数据模块⽤于保存HTTP请求数据被解析后的各项请求元素信息。HttpResponse模块这个模块是HTTP响应数据模块⽤于业务处理后设置并保存HTTP响应数据的的各项元素信息最终会被按照HTTP协议响应格式组织成为响应信息发送给客⼾端。HttpContext模块这个模块是⼀个HTTP请求接收的上下⽂模块主要是为了防⽌在⼀次接收的数据中不是⼀个完整的HttpServer模块这个模块是最终给组件使⽤者提供的HTTP服务器模块了⽤于以简单的接⼝实现HTT服务器的搭建。在之后具体的模块开发时会对每个模块的具体实现做出规划并解释功能与意义以便我们更好的梳理项目结构。总结我们的项目前提就到这里了下期我们将学习一些项目中需要实现的小功能模块正式开启我们的项目实现感谢您的收看让我们下期再见。