# HttpServer
**Repository Path**: baichayo/http-server
## Basic Information
- **Project Name**: HttpServer
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2023-10-14
- **Last Updated**: 2023-10-28
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
WebServer 总结
本项目参考自 [[A C++ High Performance Web Server](https://github.com/linyacool/WebServer#a-c-high-performance-web-server)] 和 [陈硕的 muduo 网络库](https://github.com/chenshuo/muduo) 。
本项目为实现了一个简单的Web服务器,支持 get 请求,可处理静态资源。
## 项目构成
项目主要由以下几个部分构成:
- 用 RAII (资源获取即初始化)思想封装的 锁 和 状态变量,以方便地管理多线程。
- 封装了线程和实现了线程库:以方便和安全地使用线程。
- 日志库:实现了异步日志功能,采用了 C++ stream 风格,使用起来简单。
- 定时器: 使用基于小根堆的定时器管理定时事件。
- 状态机:用于解析HTTP请求,支持 get 请求。
- 网络库:自动管理连接请求,避免和 socket 打交道。
## 采用的技术
- 基于 生产者-消费者 思想 ,使用**双缓冲技术**实现 异步日志系统
- 使用**多线程**充分利用多核CPU,并使用**线程池**避免频繁创建和销毁线程造成的开销
- 使用**智能指针**管理对象,减少资源泄露的风险
- 使用 **I/O 复用**技术(Epoll + Edge Trigger )+ **非阻塞 I/O** ,实现 Reactor 模式
- 利用了 STL 的优先队列实现定时器关闭超时请求
- 使用状态机解析HTTP请求,分为主状态机和从状态机
- 主线程只负责 监听套接字 accept 请求,使用消息队列和 Round Robin 将请求分发给 I/O 线程
- 使用eventfd实现了线程的异步唤醒
- 设置连接上限,避免文件描述符耗尽,导致客户端忙等
## 详细设计
### 日志库
日志库由五部分构成:FileUtil LogFile LogStream AsyncLogging Logging
#### FileUtil 和 LogFile
FileUtil 封装了文件的打开和写入操作,这是最底层的类。
LogFile 则进一步封装了 FileUtil ,对文件操作加锁,避免竞态条件。并且每写几次就调用 flush ,确保日志的及时性。
#### LogStream
LogStream 有两个类:FixedBuffer 和 LogStream 。
FixedBuffer 的作用是封装了一个缓冲区,体现了 RAII 思想,有效地避免内存泄漏,也大大地方便了缓冲区的使用,往缓冲区写东西只需调用 append() 即可。
LogStream 则主要是重载 `<<` 运算符,用户调用 `<<` 即可向缓冲区写入东西。
#### AsyncLogging
AsyncLogging 是异步日志库的核心。AsyncLogging 对外表现为 双缓冲区,但实际上可以看作生产者-消费者模型,缓冲区上限为 25 (超过这个上限大概率是程序遇到bug导致疯狂写日志)。用 `vector` + `shared_ptr` 管理缓冲区。为了避免对象的拷贝,使用了 `std::move` 。
AsyncLogging 会单独占用一个线程,不会阻塞主线程 I/O 。
AsyncLogging 可以看作 日志系统的后端,负责将缓冲区的内容写进文件。
#### Logging
Logging 最终实现了日志功能。用户需要打日志,只需要一条语句即可。
```
LOG << "log something";
```
这条语句会生成临时的 Logger 对象,并返回 成员 LogStream 对象。因此 依靠重载的 `<<` 实现往缓冲区内写东西。
Logging 还会给日志加上时间戳和打日志的代码位置。在生成临时对象时,先加上时间戳,待语句调用完毕,临时对象消失调用析构函数,再往缓冲区加上代码位置,最后调用 `AsyncLogger_->append()` 写日志。代码无不体现了 RAII 思想,科学地管理对象。
为了保证全局只有一个 AsyncLogging 对象,`pthread_once` 。
可以将 logging 看作日志系统的前端,负责将东西写进缓冲区。
### Reactor 模式
这是该项目的核心结构。构成 Reactor 模式的主要有三部分:EventLoop Channel 和 Epoll
#### Epoll
Epoll 是IO multiplexing的封装,管理连接和事件。采用了 I/O复用 + 非阻塞 I/O ,当对应事件发生时,通知给 Channel。
#### Channel
Channel 可以看作是连接的封装,负责处理发生的监听事件。对应的事件发生时,会调用用户注册的回调函数来完成相应的动作。
#### EventLoop
该项目采用的是 one loop per thread 思想,EventLoop 做到了这点。如果在一个线程创建多个 EventLoop 对象,程序会直接报错退出。
创建 EventLoop 对象时,会跟着创建 Epoll 对象 和 用来唤醒线程的 Channel 对象 pwakeupChannel_。
EventLoop 只会工作在 IO 线程,这样能避免锁的争用。如果 EventLoop 不在 IO 线程,EventLoop 会退出什么也不做。
当一个连接到来时,会accept 分配文件描述符,并分配Channel对象,将 Channel 对象加入 Epoll 监听队列,事件发生时,Channel 根据所发生事件调用回调函数。
EventLoop Channel 和 Epoll 构成了简单的 Reactor 模式,值得注意的是,对于用户来说,使用它更像 Proactor 模式。用户并不关心事件什么时候发生,用户只需将要完成的操作告诉程序,待相应事件发生时,程序会自动调用相应操作。
### 定时器
使用基于小根堆的定时器关闭超时请求,直接用了 STL 的 priority_queue 。增加定时事件和删除定时事件的复杂度都是 O(nlogn) 。
### HTTP状态机
虽然这是一个 webserver ,但这个项目目的在于属于网络编程,解析 HTTP 并没自己实现。
### 线程池
线程池采用了最基础的 Round Robin 方式分配任务。
## 项目难点
- 对于多线程中可能产生的竞态条件仍然分析不来。什么时候该加锁,锁的区域大小多少合适仍然分析不清楚。
- 对象的管理:该项目的耦合度比较高,对象互相嵌套,虽然利用智能指针管理了对象来避免资源泄露等问题,但对象的生命周期也更难分析了。
- 还有一些实现看不懂,如负责保存线程信息的 CurrentThread
## 项目待改进的地方
- 定时器是基于优先队列实现的,并且采用的是惰性删除,时间精度不是很高,之后可以换成基于 Timing Wheel 的实现,可以有效减少复杂度。
- 线程池使用了最简单的 Round Robin 方式分配任务,以后或许可以添加算法做到负载均衡。
- 非阻塞IO 应该搭配 应用层 buffer 避免阻塞,该项目尚未实现。
## 收获
- 让我对 RAII 思想有了充分的理解,利用 RAII 风格编写代码能有效管理对象。
- 实践了许多 C++11 的特性,如智能指针,移动拷贝。
- 学会了如何打日志。
- 对网络连接的管理有了更加深刻的认识。在网络中可能会遇到串话,粘包问题。
- 学会了 makefile 和 Cmake