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

基于多路复用技术的高并发服务器组件

目标

实现基于Reactor模型的One Thread One Loop可直接使用的高并发服务器组件,通过这个组件,可以高效简便的进行一个高性能应用服务器的搭建。

前言

HTTP协议
HTTP(超文本传输协议)是一种客户端到服务器端的请求响应协议,它通常运行在TCP之上,这个协议定义了客户端可以发送什么样的消息给服务器以及服务器返回的响应消息,所以HTTP服务器本质上就是一个TCP服务器。
Reactor模型
Reactor模型的核心是事件驱动机制,通过监听 I/O 事件(连接建立,数据读写等),将事件分发给相应的处理器(Handler)进行处理,主要由两个部分组成,Reactor部分用于监听事件,Handler部分主要用于事件处理。
Reactor模型有三种模式,分别为单Reactor单线程模型,单Reactor多线程模型,主从Reactor模型,本项目主要实现的是基于主从的Reactor模型,由于是服务器组件,并不提供业务层工作线程池的实现,用户可自行实现业务层面上的线程池。

功能划分

Server大模块

Buffer模块

Buffer模块是一个缓冲区模块,用于实现通信中发送缓冲区和接收缓冲区的功能。
在这里插入图片描述
成员变量
存放数据的std::vector< char > _buffer,由于接收到的数据有时候存在’\0’,所以不能使用string,只能使用vector< char >。
读位置 _rd_pos。
写位置_wr_pos。
代码:C++项目:Buffer模块的的设计

Socket模块

Socket模块是对套接字操作封装的一个模块,最后提供两个总接口,一个是CreateClient接口,用于创建一个通信套接字,另外一个是CreateServer,用于创建一个监听套接字。
在这里插入图片描述
成员变量
套接字socket。
代码:C++项目:Socket模块的设计

Channel模块

这个模块主要用于一个描述符所需要进行的IO事件的管理,实现对描述符可读事件,可写事件,错误事件,关闭事件,任意事件的管理操作,当Epoll模块根据IO事件监控就绪后,再回调不同事件处理的函数。
在这里插入图片描述
成员变量
需要监听的事件_events,用于传入epoll模块进行监听。
已经就绪的事件_revents,在epoll等待成功之后会返回对应的就绪事件,我们可以对它进行修改,为后续事件的执行提供了前置条件。
五个就绪事件的回调函数,包括读事件回调,写事件回调,错误事件回调,任意事件回调,关闭事件回调。
文件描述符socket,每一个Channel都管理一个文件描述符。
Eventloop对象的指针_loop,可以用于将Channel模块与绑定的Eventloop模块关联。
代码:C++项目:Channel模块的设计

Epoll模块

Epoll模块主要用于实现epoll的IO事件的添加,删除,监控。
在这里插入图片描述
成员变量
epoll模型里调用epoll_create创建的_epfd句柄。
文件描述符映射到Channel*的哈希表_events。
返回活跃事件存放的数组_epollevents。
代码:C++项目:Epoll模块的设计

TimeWheel模块

TimeWheel模块分了两个类,第一个类Task封装了任务模块,TimerTask封装了定时器模块
Task模块
这个模块主要是用于封装一个需要定时执行的函数,并采用ID标识这个任务的身份
在这里插入图片描述
成员变量
标识一个定时任务的_id,每一个id都唯一标识一个定时任务。
延迟时间_delay,标识这个定时任务在不活跃_delay时间后被执行。
执行任务的函数_dealfunc,需要自行传入。
释放此定时任务的函数_releasefunc,在TimeTask的哈希表中保存着定时任务的id和映射的Weakptr,在这个定时任务销毁之后,需要移除此定时任务在TimeTask里的id。
标识这个定时任务是否已经被取消_isfree,若这个定时任务被取消,那么不再执行_dealfunc方法,只执行_releasefunc方法。
TimeTask模块
这个模块主要用于处理服务器中的非活跃连接的释放任务,若是一个任务被添加到映射表中开始计时,若是在超时之前没有被刷新,那么这个任务就会被释放,如果在超时之前任务被刷新,那么就可以重新计算超时时间。
创建的_timer描述符被Channel管理起来,添加到Epoll模块监控起来,每一秒都会自动往这个描述符里写入一个数据,让这个描述符可读的数据+1,并触发可读事件,当读取的时候,读出的数据是多少,就说明超时了多久,相应的就需要多执行几步任务,_tick就应该往后移动多少步。
在这里插入图片描述
成员变量
一秒一秒往后走的指针_tick,每走一步都会释放掉vector数组里对应下标的数据。
最大可计时的时间_capacity,一个定时任务最多可以延迟_capacity秒。
时间轮_TimerWheel,用于保存任务所对应的sharedptr对象,每当需要将一个任务的sharedptr对象放入时间轮里的时候,就直接插入当前对应下标的vector数组的末尾。
任务映射表_Timers,保存现在需要监控的所有任务。
定时器对象描述符_timer,此描述符用于执行定时任务,每秒会自动往里面写入一次数据,里面的数据就+1,而我们设置了_timer监听读事件,所以可以在就绪的时候读取超时的次数。
管理_timer描述符的Channel对象,用于设置监听读事件,和读事件的回调函数。
用于将定时器模块和Eventloop模块关联起来的_loop,每一个定时器对应着唯一一个loop。
代码:C++项目:TimeWheel模块的设计

Eventloop模块

Eventloop模块主要是为了保证整个服务器的线程安全问题,因此要求使用者对于Connection模块里的所有操作都必须在对应的线程中运行,所以每一个Connection都必须绑定到一个Eventloop对象上。
在这里插入图片描述
成员变量
用于事件通知的描述符_eventfd,由于epoll并不是每一次等待的时候都可以等待到数据,为了不让epoll_wait阻塞,所以在每一次将任务压入任务队列之后,都要往_eventfd里写入一次数据,这样,epoll_wait至少可以等到一次事件就绪,因此不会阻塞,这个文件描述符用_event_ch的Channel对象管理起来,更便于操作。
用于等待事件就绪的_epoll,可以往_epoll里添加需要监控的描述符,便于在新的Connection绑定Eventloop的时候,往Epoll监控模块里添加需要监控的描述符,也可以删除不需要监控的描述符。
用于执行定时任务的_timer,Eventloop模块需要时时刻刻保证内部监控的都是活跃连接,需要及时释放非活跃连接,在启动非活跃连接释放的情况下,每一个Connection绑定Eventloop的时候,就需要将释放的操作添加到定时器中,若是遇到事件刷新,那么就将释放任务的事件的执行事件重新计算,这样即可保证Eventloop里的都是活跃连接。
PendingTask队列,每一个Connection模块都会绑定一个Eventloop对象,组件使用者对于Connection的每一个操作都要放在Eventloop对象所在的线程里,这样就可以避免线程安全问题。
代码:C++项目:Eventloop模块的设计

Connection模块

这个模块主要是用于一个描述符的管理
在这里插入图片描述
成员变量
一个Connection对象的_conn_id,这个id被Connection和定时器任务的id共用,用于管理。
一个通信套接字_sock,这个通信套接字被一个Channel对象_sock_ch管理起来,绑定一个Eventloop对象。
输入缓冲区_inbuffer和输出缓冲区_outbuffer,因为在通信的时候,套接字不一定可写或者可读,所以需要一个缓冲区将数据保存起来,直到套接字可读或者可写再去处理这两个缓冲区的数据。
一个Eventloop对象的指针,将这个Connection对象绑定到一个Eventloop对象上,这个Connection对象里的所有操作都必须在Eventloop对象的线程里进行,避免线程安全的问题。
_context上下文,使用一个Any对象管理起来,可以接收任意类型的协议内容,这里实现了HTTP协议,组件使用者可以添加其他不同的协议组件。
Constatu类型的状态_status,Constatu类型包括四个状态,包括DISCONNECTED,DISCONNECTTING,CONNECTED,CONNECTTING这四个状态,例如在构造Connection对象的时候处于CONNECTTING状态,而调用了回调函数Establish执行成功之后,状态就变成了CONNECTED状态,在调用Shutdown函数成功之后状态就变成DISCONNECTTING,当调用完Release函数之后,状态就会变成DISCONNECTED。
五个回调函数,当epoll_wait等待IO事件就绪的时候,就会调用相应的回调函数,比如说读事件就绪就调用读回调函数,写事件就绪就调用写回调函数等等。
五个组件使用者定义的函数,当Connection调用了构造函数,并设置了相应的五个阶段定义函数(可以不定义),那么此时组件使用者可以直接调用Establish函数,将CONNECTTING变为CONNECTED状态,并启动读事件监控,若有定义ConnectedCallBack,那么就会调用对应的_conn_cb函数,相应的,若接收缓冲区中有数据,触发了HandlerRead事件,并且定义了MessageCallBack的函数,那么就会调用对应的_msg_cb函数,剩余的_close_cb,_server_close_cb和_any_cb同上。
代码:C++项目:Connection模块的设计

LoopThread模块

这一个模块包括两个类,一个是Loopthread,主要用于管理一个线程,只有成功在这个线程中创建了Eventloop对象,才会解除条件变量的阻塞,返回对应的Eventloop的指针。另外一个是LoopThreadPool,主要用于创建,管理,分配主线程和附属线程池
在这里插入图片描述
代码:C++项目:LoopThread模块设计

Acceptor模块

此模块用于管理监听套接字的整体管理。
在这里插入图片描述
成员变量
监听套接字_sock,用于监听连接,用一个Channel对象管理起来。
主线程_loop,Acceptor里的操作都绑定到主线程_loop上执行。
回调函数_accept_cb,由于接收完新连接,需要为新连接创建一个Connection对象,创建Connection的操作是需要外部传入的。
代码:C++项目:Acceptor模块设计

TCPServer模块

在这里插入图片描述
成员变量
_next_id是下一个分配的任务/Connection对象的id。
_mainloop在主从服务器里主要处理主线程触发的事件,如监听套接字读事件就绪调用的读回调函数等事件,若是此服务器的线程池中线程的数量为0,那么_mainloop就需要处理监听套接字的读事件,和其他套接字的各种回调事件,完成所有操作。
LoopThreadPool线程池,若这个线程池数量不为0,那么主线程处理监听套接字的回调函数等主线内容,而线程池里的线程主要处理获得的通信套接字的回调函数等各种事件。
Acceptor对象_acceptor,这个对象绑定了主线程,所有操作都在主线程中完成。
一个从Connection的id映射到对应Connection对象的sharedptr的哈希表,用于存储所有新建连接的Connection对象的sharedptr,这样能保证哈希表在删除了Connection信息后,在sharedptr计数器为0的情况下完成对Connection资源的释放操作。
代码:C++项目:TCPServer模块设计

HTTP大模块

Util模块

在这里插入图片描述
代码:C++项目:Util模块的设计

Request模块

在这里插入图片描述
成员变量
请求方法_method,包括GET,POST,HEAD,PUT,DELETE等方法。
资源路径_path,用于定位一个资源的位置。
版本_version,用于标识HTTP协议的版本。
正文_body,用于存储发送的数据。
查询字符串哈希表_params,用于存储URL除资源路径外的内容。
查询头部字段哈希表_headers,用于存储头部字段映射的关系。
代码:C++项目:Request模块设计

Response模块

在这里插入图片描述

成员变量
状态码_statu,用于描述响应报文的状态。
头部字段_headers,用哈希表进行管理,存储头部字段的映射关系。
_redirect和_redirect_url,若进行重定向,那么需要设置_redirect_url。
正文字段_body,用于存储需要发送的正文。
代码:C++项目:Response模块设计

Context模块

在这里插入图片描述
成员变量
一个_request,用于存储处理完之后的请求信息。
状态码_statucode,在处理请求的时候可能会遇到不同的错误,也可能正确的处理完数据,这些情况的状态码都是不一样的。
处理的状态_statu,包括HTTPLINE,HTTPHEAD,HTTPBODY,HTTPERROR,HTTPOVER这五种状态,用于辅助记录处理的阶段。
代码:C++项目:Context模块设计

整合模块HttpServer

测试

各类请求方法的测试

GET方法

制作一个简单的回显服务器,当进行GET方法的请求的时候,会获得如下的界面
在这里插入图片描述
这里是我们从浏览器获得的请求
在这里插入图片描述
这里是服务器回复的报文
在这里插入图片描述

POST方法

当进行POST方法的测试的时候,浏览器可以看到如下界面,可以看到最后一行是我们提交的用户名和密码。
在这里插入图片描述
这是我们从浏览器获得的请求
在这里插入图片描述
这是服务器回复的报文
在这里插入图片描述

PUT方法

DELETE方法

长连接连续请求的测试

测试源码

int main()
{Socket sock;sock.CreateClient("127.0.0.1",8888);int cnt=10;int sockfd=sock.RetSock();while(cnt--){ssize_t ret=sock.Send(sockfd,"GET /Get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n",0);if(ret<0){break;}char buffer[1024];ret=sock.Recv(sockfd,buffer,sizeof(buffer));if(ret<0){perror("recv: ");//INFO_LOG("%s",buffer.c_str());break;}//std::cout<<buffer<<std::endl;sleep(1);}while(1){sleep(1);}return 0;
}

在这里插入图片描述

http://www.dtcms.com/a/511346.html

相关文章:

  • Linux 进程控制块(PCB)解析:深入理解进程管理机制
  • 子查询总拖慢查询?把它变成连接就能解决?
  • YOLOv3 核心笔记
  • 谷歌发布首个隐私安全模型VaultGemma
  • 什么情况下可能会导致 Redis 阻塞?
  • 保姆级教程vscode创建uniapp vue3+ts+pinia项目并实现自动导入、打包功能
  • 网站标题改动网络安全工程师工作内容
  • 外贸公司网站制作公司wordpress 视频播放器插件
  • 【时时三省】(C语言基础)文件读写的出错检测
  • Visual Basic 使用公共对话框
  • Amazon Bedrock助力飞书深诺:打造电商广告智能分类的“核心引擎”
  • Android App Startup 库使用说明文档,初始化不再用Application了...
  • 【鸿蒙开发手册】重生之我要学习鸿蒙HarmonyOS开发
  • 市面上的开源 AI 智能体平台使用体验
  • 2025重庆国际工业自动化及机器人展览会将带来那些新技术新体验?
  • 电商网站的建设背景找素材去哪个网站
  • 厦门杏林建设企业网站网络营销的优势有哪些
  • 个人信息出境认证办法
  • 复杂结构数据挖掘(三)关联规则挖掘实验
  • Vue3 + Vite 生产环境缓存更新问题及自动检测方案详解
  • D3QN + 优先经验回放(PER)实现全解析:从数据树到训练循环(附伪代码与流程图)
  • 查公司的国家网站有哪些域名备案时网站名称
  • ES6+新特性:ES7(二)
  • 嵌入式开发中的YUV知识点详解
  • 【文献阅读】当代MOF与机器学习
  • Java 文档注释
  • 免费网站推广渠道西安网站建设成功建设
  • 有一个服务器,用于提供HTTP服务,但是需要限制每个用户在任意的100秒内只能请求60次,怎么实现这个功能
  • 云原生周刊:Helm 十年,成就 Kubernetes 的生态中枢
  • 线段树学习