HTTPServer改进思路2(mudou库核心思想融入)

mudou网络库思想理解

Reactor与多线程

服务器构建过程中,不仅仅使用一个Reactor,而是使用多个Reactor,每个Reactor执行自己专属的任务,从而提高响应效率。

首先Reactor是一种事件驱动处理模式,其主要通过IO多路复用机制统一监听想要关心的事件,如果关心的事件有响应后,则将该响应事件分发给进程或者线程去处理,从而提高网络服务器的性能。简单可以理解为服务器利用该模式处理多路请求,然后将它们同步的分发给线程或者进程去处理。

其次构建服务器可以使用多个Reactor,从而来提高服务器对事件的响应效率。将负责连接与客户端通信监控分离开,则是一个Reactor专门负责连处理新连接的请求事件,如果发现有新的连接,则将其分发给子Reactor中去监控,另一个Reactor则主要负责监控客户端是否发送了请求或者其他事件,如果有事件触发,则将其交给线程池处理

主线Reactor处理新连接  ---->交给子Reactor进行监控 ——>事件发生交给线程处理

最后,Reactor需要将待处理的任务交付给线程或者进程去处理,如果想要使得服务器效率高,肯定要避免线程或者进程的频繁的创建销毁,这样会占用服务器的性能。所以构建线程池与Reactor配合,有助于减少性能消耗

任务线程池的主要作用则是分配独立的线程去执行Reactor中需要处理的任务,最后将处理好的数据交给Reactor线程,让其完成对客户端的响应。

One Loop Per One Thread

主Reactor负责获取连接,获取新连接后,将新连接分发给子Reactor进行网络通信的事件处理。子Reactor监控各个描述符下的读写等事件,当事件响应的时候分发给线程池中的线程去处理。

主要思想是将某一事件所有的操作放在一个线程中进行,即一个线程对应着一种事件的处理,而每个 Reactor都可以使用线程池让其为自己服务,从而可以并发的实现请求的响应。(每个线程与一个独立的事件循环绑定)

核心思想分析

  • 事件驱动
    • 每个线程都绑定一个事件循环,处理一系列非阻塞的IO事件
    • 事件循环,则是通过事件队列接收事件,同事根据事件类型和回调函数执行该事件对应的操作
  • 线程与事件绑定
    • 线程间独立运行自己的事件循环,而不会与其他线程共享事件循环
    • 这样的好处在于,避免了线程竞争,从而提高并发效率
  • 非阻塞I/O
    • 事件循环的过程中,I/O操作都是非阻塞的,这样一来,线程就不会因为等待I/O操作,而阻塞等待
    • 实现一个线程在同一时间内,可以处理多个I/O请求,从而提高系统的吞吐量
  • 任务调度与并发
    • 任务被分发到不同的线程,各个线程可以独立处理自己的任务序列
    • 通过该方式,可以借助负载均衡机制,确保各个线程的工作量均衡,从而防止某个线程过载。
  • 编程简化
    • 一个线程对应一个事件循环,所以并发程序的设计过程中,无需考虑线程同步与锁的机制
    • 只需要关注如何处理事件以及回调函数即可

一个线程绑定的事件在服务器中通常都是I/O事件

  • 连接事件
    • 建立连接:客户端尝试连接服务器的时候,服务器的监听线程会收到一个连接建立的事件(Accept)。
    • 服务器接收这个连接后,将其分配一个工作线程去处理。(在该网络库的思想中,服务器接收连接后,会将其交给子Reactor监控,子Reactor监控到事件发生后,再从线程池中调取线程去执行)
  • 读事件
    • 数据到达:客户端发送请求数据到达服务器的时候,服务器的线程收到一个读事件,此时监控读事件的Reactor开始响应。从线程池中调用线程去读取数据,解析HTTP请求,并根据请求的内容做出对应处理
  • 写事件
    • 数据可写事件:服务器准备好响应数据,并准备将该响应数据发送给客户端的时候,服务器此时会触发写事件,表示可以将数据写入到客户端的连接中。
  • 关闭事件
    • 连接关闭事件:客户端或者服务端关闭连接的时候,触发这个关闭事件,服务器需要清理相应的资源,确保连接正常关闭,避免该连接浪费服务器资源。

项目改进架构思路

服务器架构通过多线程和事件驱动的架构来高效的处理大量并发请求。服务器同时采用非阻塞I/O模式,避免了阻塞操作带来的性能瓶颈,从而让服务器可以高效的处理大量并发请求。

主要模块功能逻辑 

Main Reactor

  • 初始化
    • 创建EventLoop实例
    • 创建并初始化Acceptor,设置监听端口和回调函数
  • 接受新连接
    • Acceptor中,监听新连接请求
    • 使用accept系统调用,同时调用回调函数,为新连接进行对应封装
  • 分发新连接
    • 主Reactor将新连接分配给Reactor,创建Channel对象管理连接

Sub Reactor

  • 初始化
    • 创建EventLoop对象
    • 创建独立线程去运行EventLoop(一个线程一个Reactor思想)
  • 处理I/O事件
    • EventLoop中通过EPOLL等待关心事件发生
    • 将发生的事件分配给Channel对象
  • 事件处理
    • Channel根据事件的类型,调用注册的回调函数
    • 读事件处理:读取数据并解析HTTP请求
    • 写事件处理:发送HTTP响应 

 Epoller逻辑

  • 初始化
    • 创建EPOLL实例
  • 管理文件描述符
    • 添加、更新和删除文件描述符
  • 等待监控事件发生
    • 利用epoll_wait 等待事件的发生

HttpServer逻辑

  • 初始化
    • 创建HttpServer实例,同时继承TcpServer的功能
    • 注册HTTP请求处理函数
  • 处理HTTP请求
    • 解析HTTP请求,生成HttpRequsest对象
    • 根据请求的路径调用相应的处理函数
  • 生成并发送响应
    • 调用处理函数后生成HttpResponse对象
    • 将响应转化为字符串的形式发送给客户端 

Main逻辑

  • 注册处理函数
    • 注册URL路径以及对应的处理函数
  • 处理具体业务逻辑
    • 根据具体的需求,处理HTTP请求并生成响应 

 代码架构设计

Server

#ifndef SERVER_HPP
#define SERVER_HPP

#include <vector>
#include <functional>
#include <memory>
#include <sys/epoll.h>
#include <unistd.h>

class Channel;
class EventLoop;

class Epoller {
private:
    int _epollFd;
    std::vector<epoll_event> _events;

public:
    Epoller();
    ~Epoller();
    void UpdateChannel(Channel* channel);
    void RemoveChannel(Channel* channel);
    void Poll(std::vector<Channel*>& activeChannels);
};

class Channel {
private:
    EventLoop* _loop;
    const int _fd;
    uint32_t _events;
    uint32_t _revents;
    std::function<void()> _readCallback;
    std::function<void()> _writeCallback;

public:
    Channel(EventLoop* loop, int fd);
    void SetReadCallback(const std::function<void()>& cb);
    void SetWriteCallback(const std::function<void()>& cb);
    void EnableReading();
    void EnableWriting();
    void DisableWriting();
    void DisableAll();
    void Remove();
    void HandleEvent();
    void Update();
    int Fd() const;
    uint32_t Events() const;
    uint32_t Revents() const;
    void SetRevents(uint32_t revents);
};

class EventLoop {
private:
    bool _quit;
    std::vector<Channel*> _activeChannels;
    Epoller _poller;

public:
    EventLoop();
    void Loop();
    void Quit();
    void UpdateChannel(Channel* channel);
    void RemoveChannel(Channel* channel);
};

class Acceptor {
private:
    EventLoop* _loop;
    int _listenFd;
    std::function<void(int)> _newConnectionCallback;

public:
    Acceptor(EventLoop* loop, int port);
    void SetAcceptCallback(const std::function<void(int)>& cb);
    void Listen();
    void HandleRead();
};

class SubReactor {
private:
    EventLoop _loop;
    std::thread _thread;

public:
    SubReactor();
    void Run();
    EventLoop* GetLoop();
    void Join();
};

class MainReactor {
private:
    EventLoop _loop;
    Acceptor _acceptor;
    std::vector<SubReactor*> _subReactors;
    int _nextReactor;

public:
    MainReactor(int port, int subReactorCount);
    void NewConnection(int fd);
    void Run();
    void JoinSubReactors();
};

#endif // SERVER_HPP

HTTP

#ifndef HTTP_HPP
#define HTTP_HPP

#include "server.hpp"
#include <unordered_map>
#include <functional>
#include <string>

class HttpRequest {
public:
    std::string _method;
    std::string _path;
    std::string _version;
    std::unordered_map<std::string, std::string> _params;
    std::unordered_map<std::string, std::string> _headers;
    std::string _body;

    // 解析请求
};

class HttpResponse {
public:
    int _status;
    std::unordered_map<std::string, std::string> _headers;
    std::string _body;

    void SetContent(const std::string& content, const std::string& type) {
        _body = content;
        _headers["Content-Type"] = type;
    }

    // 响应
};

class HttpServer : public TcpServer {
private:
    std::unordered_map<std::string, std::function<void(const HttpRequest&, HttpResponse*)>> _handlers;

public:
    HttpServer(int port);
    void RegisterHandler(const std::string& path, const std::function<void(const HttpRequest&, HttpResponse*)>& handler);
    void OnMessage(const PtrConnection& conn, const std::string& message);
};

#endif // HTTP_HPP

main

#include "http.hpp"

#define WWWROOT "./wwwroot/"

int main() {
    HttpServer server(8888);
    server.Start();
    return 0;
}

细节问题梳理

Reactor、Channel|、epoll三者结合

多Reactor模型中三者结合分析

  • 每个Reactor都拥有自己的epoll实例:具体也就是每个EventLoop(Reactor)都拥有自己属于自己的epoll实例来管理文件描述符和就绪事件
  • 每个Channel对象与一个文件描述符和一个Reactor(EventLoop)关联:新连接到来时,主Reactor接受新连接请求后,将文件描述符分配给子Reactor,子Reactor创建一个Channel对象来管理文件描述符
  • 事件循环独立运行:每个EventLoop都是独立运行,用于管理文件描述符上关心的事件

 新连接创建后其文件描述符作用

  • 主Reactor接收新连接后,accept会返回这个连接的文件描述符,然后将其分发给对应的子Reactor中
  • 子Reactor管理连接:Channel类负责将文件描述符上的(读写)事件通知到对应的处理函数

进程与线程关系梳理/新连接文件描述符管理问题 

  • 首先,服务器启动后作为一个进程运行,多个Reactor则是该进程下的线程
    • 主Reactor和子Reactor都是在服务器下作为线程执行
  • 每个Reactor线程有自己的文件描述符,管理客户端连接(自己需要关心的特定事件)
  • 新连接到来后,内核又会为新连接创建一个新的文件描述符
  • 主Reactor又会将这个新获取的文件描述符分发给子Reactor中进行管理和处理

线程池和和任务队列的处理

  • 主Reactor接收新连接后,将新连接分发给子Reactor中进行管理和处理
  • 子Reactor处理I/O事件后,将其放入任务队列
  • 线程池中的线程取出任务并执行任务 
  • 线程池与事件循环放在一起,子Reactor线程将任务交给线程池处理即可

 请求到响应流程分析

  • 服务器初始化
    • 监听端口;设置线程数量(Reactor数量)注册处理函数
    • 启动服务器接口
  • 接收新连接
    • 主Reactor监听新连接,新连接到达后通过NewConnetion方法将连接分发给从Reactor中进行管理
  • 分发连接
    • 主Reactor借助LoopThreadPool将新连接交给子Reactor中的EventLoop中进行处理
  • 处理请求
    • 子Reactor线程的EventLoop处理分配到的连接
    • 读取请求数据,同时将其解析为HttpRequest对象
    • 调用注册的处理函数对请求进行处理
  • 生成响应
    • 处理数据,根据传入的请求,生成HttpResponse对象,同时设置响应内容
  • 发送响应
    • 将生成的响应,转换为字符串格式,发送给客户端
  • 关闭连接
    • 处理请求和响应后,将连接放入到连接池中备用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/843308.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于WebGoat平台的SQL注入攻击

目录 引言 一、安装好JAVA 二、下载并运行WebGoat 三、注册并登录WebGoat 四、模拟攻击 1. 第九题 2. 第十题 3. 第十一题 4. 第十二题 5. 第十三题 五、思考体会 1. 举例说明SQL 注入攻击发生的原因。 2. 从信息的CIA 三要素&#xff08;机密性、完整性、可用性&…

JAVA:Filer过滤器+案例:请求IP访问限制和请求返回值修改

JAVA&#xff1a;Filer过滤器 介绍 Java中的Filter也被称为过滤器&#xff0c;它是Servlet技术的一部分&#xff0c;用于在web服务器上拦截请求和响应&#xff0c;以检查或转换其内容。 Filter的urlPatterns可以过滤特定地址http的请求&#xff0c;也可以利用Filter对访问请求…

[数据分析]脑图像处理工具

###############ATTENTION&#xff01;############### 非常需要注意软件适配的操作系统&#xff01;有些仅适用于Linux&#xff0c;可以点进各自软件手册查看详情。 需要自行查看支持的影像模态。 代码库和软件我没有加以区分。 不是专门预处理的博客&#xff01;&#xf…

Richteck立锜科技电源管理芯片简介及器件选择指南

一、电源管理简介 电源管理组件的选择和应用本身的电源输入和输出条件是高度关联的。 输入电源是交流或直流&#xff1f;需求的输出电压比输入电压高或是低&#xff1f;负载电流多大&#xff1f;系统是否对噪讯非常敏感&#xff1f;也许系统需要的是恒流而不是稳压 (例如 LED…

Mac装虚拟机占内存吗 Mac用虚拟机装Windows流畅吗

如今&#xff0c;越来越多的Mac用户选择在他们的设备上安装虚拟机来运行不同的操作系统。其中&#xff0c;最常见的是使用虚拟机在Mac上运行Windows。然而&#xff0c;许多人担心在Mac上装虚拟机会占用大量内存&#xff0c;影响电脑系统性能。此外&#xff0c;有些用户还关心在…

k8s中部署nacos

1 部署nfs # 在k8s的主节点上执行 mkdir -p /appdata/download cd /appdata/download git clone https://github.com/nacos-group/nacos-k8s.git 将nacos部署到middleware的命名空间中 kubectl create namespace middleware cd /appdata/download/nacos-k8s # 创建角色 kub…

图论模型-迪杰斯特拉算法和贝尔曼福特算法★★★★

该博客为个人学习清风建模的学习笔记&#xff0c;部分课程可以在B站&#xff1a;【强烈推荐】清风&#xff1a;数学建模算法、编程和写作培训的视频课程以及Matlab等软件教学_哔哩哔哩_bilibili 目录 ​1图论基础 1.1概念 1.2在线绘图 1.2.1网站 1.2.2MATLAB 1.3无向图的…

ABAP打印WORD的解决方案

客户要求按照固定格式输出到WORD模板中&#xff0c;目前OLE和DOI研究了均不太适合用于这种需求。 cl_docx_document类可以将WORD转化为XML文件&#xff0c;利用替换字符串方法将文档内容进行填充同 时不破坏WORD现有格式。 首先需要将WORD的单元格用各种预定义的字符进行填充…

canvas:矢量点转栅格

案例描述 ArcGIS提供了“点转栅格”的工具,可以将矢量点转换为栅格数据,以下尝试基于canvas绘图技术,实现经纬度矢量点转换为canvas栅格数据,并在Cesium.js三维地图中进行渲染。 原始数据 转出栅格 案例分析 实现的关键点在于:如何将经纬度坐标与canvas画布坐标进…

【Vue3】工程创建及目录说明

【Vue3】工程创建及目录说明 背景简介开发环境开发步骤及源码 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日…

Figma 中文版指南:获取和安装汉化插件

Figma是一种主流的在线团队合作设计工具&#xff0c;也是一种基于 Web 端的设计工具。在当今的设计时代&#xff0c;Figma 的使用满足了每个人的设计需求&#xff0c;不仅可以实现在线编辑&#xff0c;还可以方便日常管理&#xff0c;有效提高工作效率。然而&#xff0c;相信很…

Java查询ES报错 I/O 异常解决方法: Request cannot be executed; I/O reactor status: STOPPED

问题 ES Request cannot be executed; I/O reactor status: STOPPED 报错解决 在使用ES和SpringBoot进行数据检索时&#xff0c;在接口中第一次搜索正常。第二次在搜索时在控制台就会输出Request cannot be executed; I/O reactor status: STOPPED错误 原因 本文错误是因为在使…

51单片机14(独立按键实验)

一、按键介绍 1、按键是一种电子开关&#xff0c;使用的时候&#xff0c;只要轻轻的按下我们的这个按钮&#xff0c;按钮就可以使这个开关导通。 2、当松开这个手的时候&#xff0c;我们的这个开关&#xff0c;就断开开发板上使用的这个按键&#xff0c;它的内部结构&#xff…

用Java手写jvm之实现java -version的效果

写在前面 源码 。 本文来用纯纯的Java代码来实现java -version的效果&#xff0c;就像下面这样&#xff1a; 1&#xff1a;程序 这里输出类似这样的&#xff1a; java version "9" Java(TM) SE Runtime Environment (build 9181) Java HotSpot(TM) 64-Bit Serve…

突破•指针二

听说这是目录哦 复习review❤️野指针&#x1fae7;assert断言&#x1fae7;assert的神奇之处 指针的使用和传址调用&#x1fae7;数组名的理解&#x1fae7;理解整个数组和数组首元素地址的区别 使用指针访问数组&#x1fae7;一维数组传参的本质&#x1fae7;二级指针&#x…

filebeat,kafka,clickhouse,ClickVisual搭建轻量级日志平台

springboot集成链路追踪 springboot版本 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.3</version><relativePath/> <!-- lookup parent from…

python—爬虫爬取电影页面实例

下面是一个简单的爬虫实例&#xff0c;使用Python的requests库来发送HTTP请求&#xff0c;并使用lxml库来解析HTML页面内容。这个爬虫的目标是抓取一个电影网站&#xff0c;并提取每部电影的主义部分。 首先&#xff0c;确保你已经安装了requests和lxml库。如果没有安装&#x…

HTML零基础自学笔记(上)-7.18

HTML零基础自学笔记&#xff08;上&#xff09; 参考&#xff1a;pink老师一、HTML, Javascript, CSS的关系是什么?二、什么是HTML?1、网页&#xff0c;网站的概念2、THML的基本概念3、THML的骨架标签/基本结构标签 三、HTML标签1、THML标签介绍2、常用标签图像标签&#xff…

数据结构----算法复杂度

1.数据结构前言 数据是杂乱无章的&#xff0c;我们要借助结构将数据管理起来 1.1 数据结构 数据结构(Data Structure)是计算机存储、组织数据的⽅式&#xff0c;指相互之间存在⼀种或多种特定关系的数 据元素的集合。没有⼀种单⼀的数据结构对所有⽤途都有⽤&#xff0c;所…

ranger审计日志对接CDH solr

作者&#xff1a;耀灵 一、准备条件 1、已安装完毕ranger-admin 2、已在CDH上部署solr&#xff08;注意在安装solr时更改下solr在zk上的节点信息&#xff09; 二、更改相关配置 1、修改ranger-2.1.0-admin/contrib/solr_for_audit_setup/install.properties SOLR_USERsolr …