¶SimpleWebServer
¶说明
该项目是对于 markparticle/WebServer 的复现及修改,对个人启发很大。
主要目的是辅助学习并实践网络编程,搭建 C++ web 服务器。
完成了以下环节:Buffer、Log、线程池、定时器、IO 复用、HTTP。
项目地址 🏡 : SimpleWebServer
¶Buffer
Buffer 布局
初始情况下, readPos_ = writePos_ = start = 0
;
读写操作
一次读写操作,Buffer 内部变化如下:
Makespace 操作
如果写入长度: $Writable < L2 \leq Prependable + Writable$,利用 Prependable 中内容扩容。本质就是将 [readPos_, writePos_ )
中的内容 copy 到 start 起点处。
如果写入长度: $L3 \geq Prependable + Writable$,则扩大 Buffer 大小:writePos_ + L3 + 1
¶Log
利用单例模式与阻塞队列 ( Blockqueue ) 实现异步日志系统,记录服务器运行状态。
关于队列的操作都需要加锁!特别是 push 和 pop 操作,利用两个条件变量实现生产者消费者模式。
push 操作,主要是通过 Log::write
函数触发,代码中的 Log 内容先输入到 Buffer 中,后将 Buffer 中内容 push 到 Blockqueue 中。
Log 写线程 thread(FlushLogThread)
循环调用 pop 操作。pop 功能是将 Blockqueue 中相应内容取出,并最后写入到日志文件中。
¶线程池
将锁 mtx
、条件变量 cond
、关闭标记 isClosed
和任务队列 tasks
集成与一个结构体 Pool 中,并由智能指针 shared_ptr pool_
指向该结构体。
线程池中线程数量: threadCount
,通过 for 循环创建对应数量的线程。
以 lambda 函数作为线程入口函数创建线程 std::thread([pool = pool_] {...}).detach();
,并设置线程分离。其中所有的线程共享 pool_
,他们使用共同的 Pool 结构体。
lambda 函数内部是一个从任务队列取任务处理的过程。对任务队列的处理需要进行加锁,如果没有工作需要阻塞等待任务队列 AddTask 。
¶定时器
基于最小堆实现定时器,用来关闭超时的非活动连接;
最小堆:父节点的值比每一个子节点的值都小! 🙋♂ 注意:堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。 数据结构:堆(Heap)
主要功能 :
- 堆的调整函数,上滤
siftup
,下滤siftdown
- 节点的添加
add
,删除del
,(增量)调整adjust
- 删除特定节点并触发回调
doWork
,心搏函数tick
- 其他函数:
pop
、clear
、交换节点SwapNode
、获取距离最近一次超时的间隔GetNextTick
添加节点 : AddClient_
操作时会调用 add 函数,同时会添加回调函数 CloseCoon_
操作1:如果是新节点 (以 fd 标记新旧节点 ),默认添加在堆尾,之后进行上滤操作即可。
操作2:如果是已有节点,也就是修改操作,需要根据情况进行调整:
🔹 非叶子节点,且已经成功下滤,则无需进行上滤
🔸 非叶子节点,但是没有成功下滤,则需要进行上滤
🔹 叶子节点,无下滤操作,需要进行上滤
1 | if(!siftdown_(i, heap_.size())) { |
删除节点 :将要删除的节点换到队尾,之后调整堆,后与添加操作 2 相同。
增量调整 :只要进行下滤操作即可。每次 DealRead_
和 DealWrite
操作就延后超时时间。
心搏函数 :从头节点开始遍历,清除超时的定时器,并调用对应的回调函数。
¶IO 复用
利用IO复用技术 epoll 与线程池实现多线程的 Reactor 高并发模型
¶HTTP
HTTP 部分主要处理 用户的 HTTP 的请求并作出响应,其工作流程如下图所示:
process 的处理借助了 httprequest
类和 httpresponse
类,分别用来解析 HTTP 请求和生成对应的 HTTP 响应。
¶致谢
再次感谢!@MARK