一、基础知识
1. 服务器编程基本框架

服务器的结构通常如上图,主要有 I/O 单元、逻辑单元和网络存储单元组成,每个单元之间通过请求队列进行通信,从而协同完成任务。
在项目中,I/O 单元用于处理客户端连接,读写网络数据;逻辑单元用于处理业务逻辑;网络存储单元用于存储本地数据库和文件。
模块 | 单个服务器程序 | 服务器机群 |
I/O 处理单元 | 处理客户连接,读写网络数据 | 作为接入服务器,实现负载均衡 |
逻辑单元 | 业务进程或线程 | 逻辑服务器 |
网络存储单元 | 本地数据库、文件或缓存 | 数据库服务器 |
请求队列 | 各单元之间的通信方式 | 各服务器之间的永久 TCP 连接 |
2. 五种 I/O 模型
- 阻塞 I/O :调用函数后,必须等待调用函数返回后才进行下一步操作;
- 非阻塞 I/O :调用函数后,每隔一段时间检测 I/O 事件是否就绪,若没有就绪则可以做其他事情;若就绪则继续执行后续代码。非阻塞 I/O 执行系统调用总是立即返回,而不管 I/O 事件是否就绪,需要根据 errno 区分具 I/O 事件是否就绪;
- 信号驱动 I/O :调用函数后,不再主动检测 I/O 事件是否就绪,而是等待事件完成或发生错误后进程收到的信号来判断 I/O 事件是否就绪,因此该方法是非阻塞的;
- I/O 复用:主要指 Linux 使用的 3 个函数,它们均能同时监听多个事件:
- select :会使进程阻塞,没有将文件描述符和事件进行绑定,它仅仅是一个文件描述符的集合,因此使用时需要遍历所有事件,来查找目标事件;
- poll :会使进程阻塞,将文件描述符和事件绑定,所有事件都被统一处理,使用时同样需要遍历所有事件,来查找目标事件;
- epoll :在内核中维护一个事件表,提供
epoll_ctl方法来控制其中的事件(增、删、改),使用时只需要遍历就绪事件(前 n 个)。
- 异步 I/O :Linux 可以调用
aio_read函数告诉内核描述字缓冲区指针和缓冲区大小、文件偏移及通知的方式,然后立即返回,当内核将数据拷贝到缓存区后在通知应用程序。
前四种 I/O 模型都是同步的,即内核向应用程序通知的是就绪事件,需要用户代码对其执行操作;异步 I/O 则是指内核像应用程序通知完成事件,即内核已完成 I/O 操作。
3. 事件处理模式
- reactor :主线程(I/O 处理单元)只负责监听文件描述符上是否有事件发生,有的话立即通知工作线程(逻辑单元),所有 I/O 具体的操作均在工作线程中完成,通常为同步的模型;
- proactor :主线程和内核负责处理 I/O 的具体操作(如读写数据),工作线程仅负责业务逻辑(如处理客户请求),通常为异步的。由于异步 I/O 模型并不成熟,因此项目中使用同步 I/O 模拟实现 proactor 模式。
同步 I/O 模型的工作流程如下:
- 主线程往 epoll 内核事件表注册 socket 上的读就绪事件;
- 主线程调用 epoll_wait 等待 socket 上有数据可读;
- 当 socket 上有数据可读,epoll_wait 通知主线程,此时主线程从 socket 循环读取数据,读完后将数据封装成一个请求对象插入请求队列;
- 睡眠在请求队列上的某个工作线程被唤醒,它获得请求对象并处理,然后往 epoll 内核事件表中注册 socket 上的写就绪事件;
- 主线程调用 epoll_wait 等待 socket 可写;
- 当 socket 上有数据可写,epoll_wait 通知主线程,此时主线程往 socket 上写入服务器处理客户请求的结果。
4. 半同步/半反应堆模式
项目中使用的并发模式指 I/O 处理单元与逻辑单元的协同方式,该模型是半同步/半异步模型的变体,同步指的是程序完全按照代码顺序执行,这是一个串行的方式;异步指的是程序的执行需要由系统事件驱动,也就是说多个事件(多段代码)同时进行。
半同步/半异步模式的工作流程如下:
- 异步线程用于处理 I/O 事件;
- 异步线程监听到客户请求后,就将其封装成请求对象并插入到请求队列中;
- 请求队列将通知某个工作线程(同步模式的线程,用于处理客户逻辑)来读取并处理该请求。
半同步/半反应堆模式的工作流程如下(proactor 模式):
- 主线程充当异步线程,负责监听所有 socket 上的事件;
- 若有新请求到来,主线程接收并得到新连接的 socket ,然后往 epoll 内核事件表中注册该 socket 上的读写事件;
- 如果该 socket 上有读写事件发生,主线程从 socket 上接收数据,并将数据封装成请求对象插入到请求队列中;
- 所有工作线程(同步模式的线程)睡眠在请求队列行,当有任务到来时,通过竞争获得任务的接管权。
5. 线程池
一般的任务中,当需要使用线程处理工作时,我们才构造一个线程出来,而当任务完成时则回收该线程防止占用资源。但是这样的方式在服务器中会造成很大的时间开销,因此可以采用线程池的方式用空间换时间:
- 池表示一组资源的集合,这组资源在服务器启动之初就被创建并初始化,称为静态资源;
- 在服务器运行阶段,如有任务需要使用线程,则可以直接从线程池中获取,无需动态分配;
- 当一个线程工作完成后,无需将其销毁,只需要放入池中即可。
二、代码解析
1. 线程池类定义
2. 线程池创建与回收
3. 向请求队列中添加任务
4. 线程处理函数
5. run 执行任务
参考文献
原创存证 · 本地哈希 · d88b6ff3dfd9...
算法:SHA-256
时间:2026-10-16
哈希:
d88b6ff3dfd9d820bb9df309ab95c464bc9f6e70e11959d8381689665393f3da该信息用于辅助证明当前内容版本,不等同于法律公证或版权登记。
- 作者:BeZer0
- 链接:https://blog.bezer0.cn/%E8%80%83%E7%A0%94/3fb0bc13-b7ac-80b3-bb3f-d8f1bfcbd50c
- 声明:本文为原创文章,采用 CC BY-NC-SA 4.0 协议,转载请注明作者和原文链接。

