参考我自建的tinyserver_built
项目结构
项目一般包含以下主要目录和文件:
src/
:源代码目录,包含服务器实现的各个模块。root/
:网站的根目录,包含 HTML 文件、图片等静态资源。
1.主要模块
1. main.cpp
- 启动服务器的入口点,初始化服务器并开始监听端口。
2. src/server/webserver.cpp
和 webserver.h
WebServer
类:负责服务器的初始化、资源加载、事件循环等核心功能。
3. src/http/http_conn.cpp
和 http_conn.h
http_conn
类:处理 HTTP 连接,包括解析请求、生成响应。
4. src/log/log.cpp
和 log.h
- 日志系统:用于记录服务器运行时的信息、错误和调试信息。
5. src/lock/locker.h
- 锁机制:实现线程间同步,防止资源竞争。
6. src/threadpool/threadpool.h
- 线程池:管理工作线程,执行并发任务。
7. src/timer/lst_timer.cpp
和 lst_timer.h
- 定时器:管理连接的超时处理,保持长连接的状态。
2. 代码解析
1. main.cpp
int main(int argc, char *argv[]) {
WebServer server;
server.init(); // 初始化服务器
server.start(); // 启动服务器
return 0;
}
2. WebServer
类
void WebServer::init() {
// 初始化日志、线程池、数据库连接池等
}
void WebServer::start() {
// 事件循环,处理客户端连接
while (true) {
int event_number = epoll_wait();
// 处理每个事件,如读写事件、定时器事件
}
}
3. http_conn
类
void http_conn::process() {
// 解析请求行、请求头和请求体
// 根据请求类型生成响应
}
bool http_conn::read() {
// 从客户端读取数据
}
bool http_conn::write() {
// 向客户端写入数据
}
4. 日志系统
void Log::init() {
// 初始化日志文件
}
void Log::write_log() {
// 将日志写入文件
}
3. 运行流程
- 启动服务器:初始化日志、线程池、数据库连接池等资源。
- 等待连接:使用
epoll
等机制等待客户端连接。 - 处理请求:当有新连接时,创建
http_conn
实例处理请求。 - 生成响应:根据请求类型生成相应的响应并返回给客户端。
- 日志记录:记录运行中的重要信息和错误。
线程池
在线程池部分做几点解释,然后大家去看代码的时候就更容易看懂了:
- 所谓线程池,就是一个
pthread_t
类型的普通数组,通过pthread_create()
函数创建m_thread_number
个线程,用来执行worker()
函数以执行每个请求处理函数(HTTP请求的process
函数),通过pthread_detach()
将线程设置成脱离态(detached)后,当这一线程运行结束时,它的资源会被系统自动回收,而不再需要在其它线程中对其进行pthread_join()
操作。 - 操作工作队列一定要加锁(
locker
),因为它被所有线程共享。 - 我们用信号量来标识请求队列中的请求数,通过
m_queuestat.wait();
来等待一个请求队列中待处理的HTTP请求,然后交给线程池中的空闲线程来处理。
threadpool.cpp
总体流程
threadpool-----
1、创建一个描述线程池的大小为 m_thread_number
的线程数组 m_threads
2、循环创建 m_thread_number
个线程,每个线程运行 worker
函数。
等一下,首先讲讲任务队列
多线程服务器中,任务队列和线程池数组中的线程之间的关系
-
任务队列
- 任务队列 (
m_workqueue
) 是一个共享资源,保存着所有待处理的任务。 - 每当有新任务到来时,会将任务添加到队列中。
线程池数组
- 线程池数组 (
m_threads
) 包含了多个线程,每个线程都是在初始化时创建的。 - 这些线程不断地从任务队列中取出任务进行处理。
- 任务队列 (
总结来说:就是线程数组池的线程从任务队列中取出任务来处理
同步和互斥:为了防止多个线程同时访问任务队列而产生竞态条件,需要使用互斥锁 (m_queuelocker
) 和信号量 (m_queuestat
) 来进行同步。
继续
3、任务添加,当有新的请求到来时,调用 append
或 append_p
方法,将请求添加到任务队列中,并通知线程池有新的任务需要处理
4、工作线程:worker
和run
方法来执行任务
Reactor 和 Proactor 模型
- Reactor 模型:线程会根据请求的状态(读或写)分别处理。如果是读操作(
m_state == 0
),先读取数据,然后处理请求;如果是写操作,直接处理写操作。 - Proactor 模型:线程直接处理请求。
总结
通过线程池管理多个工作线程,服务器能够高效地处理并发请求。线程池的工作机制包括任务添加、线程启动、任务处理和选择不同的处理模型(Reactor 或 Proactor)。
为什么要使用线程池?
当你需要限制你应用程序中同时运行的线程数时,线程池非常有用。因为启动一个新线程会带来性能开销,每个线程也会为其堆栈分配一些内存等。为了任务的并发执行,我们可以将这些任务任务传递到线程池,而不是为每个任务动态开启一个新的线程。
线程池中的线程数量是依据什么确定的?
在StackOverflow上面发现了一个还不错的回答,意思是:
线程池中的线程数量最直接的限制因素是中央处理器(CPU)的处理器(processors/cores)的数量N
:如果你的CPU是4-cores的,对于CPU密集型的任务(如视频剪辑等消耗CPU计算资源的任务)来说,那线程池中的线程数量最好也设置为4(或者+1防止其他因素造成的线程阻塞);对于IO密集型的任务,一般要多于CPU的核数,因为线程间竞争的不是CPU的计算资源而是IO,IO的处理一般较慢,多于cores数的线程将为CPU争取更多的任务,不至在线程处理IO的过程造成CPU空闲导致资源浪费,公式:最佳线程数 = CPU当前可使用的Cores数 * 当前CPU的利用率 * (1 + CPU等待时间 / CPU处理时间)
(还有回答里面提到的Amdahl准则可以了解一下
http解析与响应连接
process()
1、在线程池的run()
方法中调用了process
来处理http请求任务
2、方法process_read()
来解析HTTP报文从而解析HTTP请求,解析成功后,modfd
函数用于修改 epoll 事件表,将当前连接的事件修改为读事件 (EPOLLIN
) 或写事件 (EPOLLOUT
)。
3、process_read()
函数的作用就是将请求报文进行解析,因为用户的请求内容包含在这个请求报文里面,只有通过解析,知道用户请求的内容是什么,是请求图片,还是视频,或是其他请求,我们根据这些请求返回相应的HTML页面等
讲下HHTP请求类型
GET 请求
- 目的:从服务器获取资源。
- 特点:数据通过 URL 参数传递,长度有限。
- 示例:访问网页、查询数据等。
POST 请求
- 目的:向服务器提交数据。
- 特点:数据通过请求体传递,适合传输大数据量和敏感信息。
- 示例:提交表单、上传文件等。
4、经过上述解析,当得到一个完整的,正确的HTTP请求时,就到了do_request
方法
5、首先对GET请求和不同POST请求(登录,注册,请求图片,视频等等)做不同的预处理
6、生成响应,process_write
,根据解析结果生成相应的 HTTP 响应,包括状态行、头部和内容。
先来看看这些不同请求是怎么来的
假设你已经搭好了你的HTTP服务器,然后你在本地浏览器中键入localhost:9000
,然后回车,这时候你就给你的服务器发送了一个GET请求,什么都没做,然后服务器端就会解析你的这个HTTP请求,然后发现是个GET请求,然后返回给你一个静态HTML页面,也就是项目中的judge.html
页面
那POST请求怎么来的呢?这时你会发现,返回的这个judge
页面中包含着一些新用户
和已有账号
这两个button
元素,当你用鼠标点击这个button
时,你的浏览器就会向你的服务器发送一个POST请求,服务器段通过检查action
来判断你的POST请求类型是什么,进而做出不同的响应。
处理流程总结
- 初始化连接:设置套接字和地址,添加文件描述符到 epoll 中,增加用户计数。
- 读取请求:从客户端读取数据,判断是水平触发(LT)还是边缘触发(ET)。
- 解析请求:根据 HTTP 请求行、请求头和请求内容的状态逐步解析完整的 HTTP 请求。
- 生成响应:根据解析结果生成相应的 HTTP 响应,包括状态行、头部和内容。
- 主处理流程:调用
process_read
读取和解析请求,如果请求不完整则继续等待;调用process_write
生成并发送响应,如果发送失败则关闭连接。
数据库连接池
https://huixxi.github.io/2020/06/02/%E5%B0%8F%E7%99%BD%E8%A7%86%E8%A7%92%EF%BC%9A%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82%E7%A4%BE%E9%95%BF%E7%9A%84TinyWebServer/#more
未完待续