diff --git a/操作系统/00_课程导航.md b/操作系统/00_课程导航.md
index f065a3d..3acda8a 100644
--- a/操作系统/00_课程导航.md
+++ b/操作系统/00_课程导航.md
@@ -31,6 +31,10 @@ mindmap
并发服务器(多进程/多线程/预线程化)
系统底层
程序代码优化
+ 操作系统实践
+ Web服务器初步实现
+ 多进程多线程服务器
+ 线程池与业务分割模型
```
---
@@ -139,6 +143,16 @@ FAT·Ext2·RAID"]
---
+## 🖥️ 操作系统实践
+
+| 实践 | 主题 | 核心内容 | 涉及章节 |
+|------|------|----------|----------|
+| [[实践01_Web服务器初步实现]] | Web服务器初步实现 | 单进程迭代式服务器、HTTP请求处理、RIO函数库、性能测试与优化 | 第09讲、第10讲、第18讲 |
+| [[实践02_多进程多线程服务器]] | 多进程多线程服务器 | fork并发、pthread并发、select/epoll多路复用、性能对比 | 第06讲、第07讲、第10讲 |
+| [[实践03_线程池与业务分割]] | 线程池与业务分割模型 | 线程池实现、预线程化、sbuf生产者消费者、业务分割模型 | 第07讲、第10讲 |
+
+---
+
## 📎 附录
- [[附录A_Wrapper库参考]] — 课程提供的C语言包装库(wrapper.h/libwrapper.a)
diff --git a/操作系统/操作系统实践/实践01_Web服务器初步实现.md b/操作系统/操作系统实践/实践01_Web服务器初步实现.md
new file mode 100644
index 0000000..f1eedd0
--- /dev/null
+++ b/操作系统/操作系统实践/实践01_Web服务器初步实现.md
@@ -0,0 +1,578 @@
+# 实验一:Web服务器的初步实现
+
+> [!info] 实验信息
+> - **课程**: 操作系统实践
+> - **实验编号**: 实验一
+> - **实验类型**: 综合性实验
+> - **关联理论**: [[09_网络编程基础]]、[[10_并发服务器]]、[[18_程序代码优化]]
+
+---
+
+## 一、实验目的
+
+1. 掌握 Linux 系统下网络编程开发环境的搭建
+2. 实现简单的单进程 Web 服务器
+3. 进行性能测试,掌握网络编程的基本技术
+4. 初步掌握 Web 服务器的编程实现和测试技术
+
+---
+
+## 二、实验环境
+
+| 项目 | 说明 |
+|------|------|
+| 操作系统 | Ubuntu / CentOS (Linux) |
+| 编译器 | GCC |
+| 测试工具 | http_load、ab、wrk |
+| 监控工具 | vmstat、iostat、gprof |
+| 服务器端口 | 8088 |
+
+---
+
+## 三、源代码文件清单
+
+```
+.
+├── common.h # 公共头文件、函数声明、常量定义(由wrapper.h简化而来)
+├── common.c # RIO函数库和打开网络连接函数源代码
+├── webclient.c # web客户端源代码
+├── webserver.c # web服务器源代码(即weblet.c)
+├── togglec.c # toggle客户端源代码
+├── togglesi.c # 迭代式toggle服务器源代码
+├── cgi-bin/
+│ └── add.c # CGI程序
+├── Makefile # 构建脚本
+├── index.html # 测试主页
+├── test.html # 测试页面
+└── urls # http_load测试用URL列表
+```
+
+> [!tip] 文件说明
+> - `common.h` / `common.c` 封装了网络编程常用的包装函数(Socket、Bind、Listen、Accept 等),是对 `wrapper.h` 的简化版本
+> - `togglec.c` / `togglesi.c` 用于演示迭代式客户-服务器模型
+> - `weblet.c` 是核心的单进程 Web 服务器实现
+
+---
+
+## 四、服务器架构
+
+### 4.1 整体架构图
+
+```mermaid
+
+graph TD
+ subgraph 客户端
+ Browser["浏览器 / Web客户端"]
+ end
+
+ subgraph Web服务器 - weblet
+ Main["main() 主循环"]
+ Accept["accept() 接受连接"]
+ PT["process_trans() 处理HTTP事务"]
+ Close["close() 关闭连接"]
+ end
+
+ subgraph 请求处理流程
+ RR["读取请求行"]
+ Judge["判断静态/动态请求"]
+ Parse["解析URI"]
+ Send["发送响应"]
+ Err["错误处理"]
+ end
+
+ subgraph 响应模块
+ FS["feed_static() 静态内容"]
+ FD["feed_dynamic() 动态内容"]
+ ER["error_request() 错误响应"]
+ end
+
+ Browser -->|"HTTP请求"| Main
+ Main --> Accept
+ Accept --> PT
+ PT --> RR
+ RR --> Judge
+ Judge -->|"静态请求"| Parse
+ Judge -->|"动态请求"| Parse
+ Parse --> Send
+ Send --> FS
+ Send --> FD
+ Send --> ER
+ FS -->|"HTTP响应"| Browser
+ FD -->|"HTTP响应"| Browser
+ ER -->|"错误页面"| Browser
+ PT --> Close
+ Close -->|"等待下一个连接"| Accept
+
+
+
+```
+
+### 4.2 HTTP请求处理流程
+```mermaid
+
+flowchart TD
+ Start(["开始: accept() 获取连接"]) --> ReadReq["1.读取请求行
+ read_requesthdrs()"]
+ ReadReq --> ParseReq["2.解析请求方法、URI、版本"]
+ ParseReq --> IsStatic{"3.is_static()
+ 判断请求类型"}
+
+ IsStatic -->|"静态请求"| ParseStatic["4a.parse_static_uri()
+ 解析静态文件URI"]
+ IsStatic -->|"动态请求"| ParseDynamic["4b.parse_dynamic_uri()
+ 解析CGI程序URI"]
+
+ ParseStatic --> OpenFile["5a.打开请求的文件"]
+ ParseDynamic --> ExecCGI["5b.执行CGI程序"]
+
+ OpenFile --> FileExist{"文件是否存在?"}
+ FileExist -->|"是"| FeedStatic["6a.feed_static()
+ 发送静态文件响应"]
+ FileExist -->|"否"| ErrorReq["6c.error_request()
+ 返回404错误"]
+
+ ExecCGI --> FeedDynamic["6b.feed_dynamic()
+ 发送CGI执行结果"]
+
+ FeedStatic --> Done(["结束: close()"])
+ FeedDynamic --> Done
+ ErrorReq --> Done
+
+
+
+```
+
+---
+
+## 五、核心源码分析
+
+### 5.1 主函数结构 (`weblet.c`)
+
+```c
+/*
+ * weblet.c - 一个简单的单进程迭代式Web服务器
+ * 服务端口: 8088
+ */
+int main(int argc, char **argv) {
+ int listen_sock, conn_sock;
+ int hit;
+
+ /* 创建监听套接字 */
+ listen_sock = open_listen_sock(port);
+
+ /* 主循环:迭代式处理每个请求 */
+ for (hit = 1; ; hit++) {
+ /* 接受客户端连接 */
+ conn_sock = accept(listen_sock,
+ (struct sockaddr *)&client_addr,
+ &client_len);
+
+ /* 处理一个HTTP事务 */
+ process_trans(conn_sock, hit);
+
+ /* 关闭连接,等待下一个请求 */
+ close(conn_sock);
+ }
+}
+```
+
+> [!warning] 迭代式服务器的局限
+> 该服务器是**单进程迭代式**的,一次只能处理一个客户端请求。当某个请求处理耗时较长时,后续请求会被阻塞等待。改进方案参见 [[10_并发服务器]] 中的多进程和多线程服务器。
+
+### 5.2 HTTP事务处理函数 (`process_trans`)
+
+```c
+/*
+ * process_trans - 处理一个HTTP事务
+ * @fd: 连接套接字描述符
+ * @hit: 请求计数
+ */
+void process_trans(int fd, int hit) {
+ int is_static; /* 是否为静态请求 */
+ struct stat sbuf; /* 文件状态 */
+ char buf[MAXLINE]; /* 读缓冲区 */
+ char method[MAXLINE]; /* 请求方法: GET */
+ char uri[MAXLINE]; /* 请求URI */
+ char version[MAXLINE]; /* HTTP版本 */
+ char filename[MAXLINE]; /* 文件路径 */
+ char cgiargs[MAXLINE]; /* CGI参数 */
+
+ /* 1. 读取并解析请求行 */
+ read_requesthdrs(buf);
+
+ /* 2. 解析请求行: GET /index.html HTTP/1.1 */
+ sscanf(buf, "%s %s %s", method, uri, version);
+
+ /* 3. 判断是否为静态请求 */
+ is_static = is_static(uri);
+
+ /* 4. 解析URI,提取文件名和CGI参数 */
+ if (is_static) {
+ parse_static_uri(uri, filename, cgiargs);
+ } else {
+ parse_dynamic_uri(uri, filename, cgiargs);
+ }
+
+ /* 5. 检查文件是否存在 */
+ if (stat(filename, &sbuf) < 0) {
+ error_request(fd, filename, "404", "Not Found");
+ return;
+ }
+
+ /* 6. 发送响应 */
+ if (is_static) {
+ feed_static(fd, filename, sbuf.st_size);
+ } else {
+ feed_dynamic(fd, filename, cgiargs);
+ }
+}
+```
+
+### 5.3 RIO函数库 (`common.c`)
+
+```c
+/*
+ * RIO (Robust I/O) 健壮的I/O函数库
+ * 解决了Unix I/O的不足:
+ * - 短读(short read)问题
+ * - 被信号中断(interrupted read)问题
+ */
+
+/* 无缓冲的RIO读取 */
+ssize_t rio_readn(int fd, void *usrbuf, size_t n);
+ssize_t rio_writen(int fd, void *usrbuf, size_t n);
+
+/* 带缓冲的RIO读取 */
+void rio_readinitb(rio_t *rp, int fd);
+ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
+ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);
+
+/* 网络连接函数 */
+int open_listen_sock(int port); /* 创建监听套接字 */
+int open_client_sock(char *host, int port); /* 创建客户端连接 */
+```
+
+---
+
+## 六、MIME类型支持
+
+服务器根据文件扩展名返回对应的 `Content-Type`:
+
+| 扩展名 | MIME类型 |
+|--------|----------|
+| `.gif` | `image/gif` |
+| `.jpg` / `.jpeg` | `image/jpeg` |
+| `.png` | `image/png` |
+| `.ico` | `image/x-icon` |
+| `.zip` | `application/zip` |
+| `.gz` / `.tar` | `application/x-tar` |
+| `.htm` / `.html` | `text/html` |
+
+---
+
+## 七、实验任务
+
+### 任务1:运行验证迭代式服务器(togglec / togglesi)
+
+> [!example] 操作步骤
+
+```bash
+# 终端1: 启动迭代式toggle服务器
+$ ./togglesi
+
+# 终端2: 运行toggle客户端
+$ ./togglec localhost
+```
+
+此任务演示了[[09_网络编程基础]]中的基本客户-服务器通信模型。`togglesi` 是一个迭代式服务器,一次只服务一个客户端。
+
+### 任务2:编写跨计算机文件传输程序
+
+实现 `ftps.c`(服务器)和 `ftpc.c`(客户端),支持以下命令:
+- `put <文件名>` —— 客户端上传文件到服务器
+- `get <文件名>` —— 客户端从服务器下载文件
+
+> [!abstract] 设计要点
+> 1. 使用 Socket API 建立 TCP 连接
+> 2. 先传输文件名和文件大小(协议头),再传输文件内容
+> 3. 使用 RIO 函数处理短读问题
+> 4. 服务器端需处理文件不存在的情况
+
+### 任务3:运行验证 Web 服务器
+
+```bash
+# 编译
+$ make
+
+# 启动Web服务器 (端口8088)
+$ ./weblet 8088
+
+# 在浏览器中访问
+# http://localhost:8088/index.html
+# http://localhost:8088/test.html
+
+# 使用curl测试
+$ curl -v http://localhost:8088/index.html
+```
+
+### 任务4:使用 http_load 进行性能测试
+
+```bash
+# 准备URL列表文件 (urls)
+$ cat urls
+http://localhost:8088/index.html
+http://localhost:8088/test.html
+
+# 并行度5,总请求数50
+$ http_load -parallel 5 -fetches 50 urls
+
+# 并行度10,总请求数50
+$ http_load -parallel 10 -fetches 50 urls
+
+# 并行度5,持续20秒
+$ http_load -parallel 5 -seconds 20 urls
+
+# 并行度10,持续20秒
+$ http_load -parallel 10 -seconds 20 urls
+```
+
+> [!note] http_load 输出指标
+> - **fetches/sec**: 每秒完成的请求数(吞吐量)
+> - **bytes/sec**: 每秒传输的字节数
+> - **msecs/connect**: 平均连接建立时间
+> - **msecs/first-response**: 首字节响应时间
+
+### 任务5:使用 vmstat、iostat、gprof 收集性能数据
+
+```bash
+# ---- vmstat: 监控系统资源 ----
+# 每2秒采样一次,共10次
+$ vmstat 2 10
+
+# 重点关注:
+# r - 运行队列长度
+# us - 用户态CPU占比
+# sy - 内核态CPU占比
+# wa - I/O等待占比
+# free - 空闲内存
+
+# ---- iostat: 监控磁盘I/O ----
+# 以KB为单位,每2秒采样一次,共10次
+$ iostat -k 2 10
+
+# 重点关注:
+# tps - 每秒传输次数
+# kB_read/s - 每秒读取量
+# kB_wrtn/s - 每秒写入量
+
+# ---- gprof: 函数级性能分析 ----
+# 编译时加 -pg 选项
+$ gcc -pg -o weblet weblet.c common.c
+
+# 运行服务器并处理若干请求后终止
+$ ./weblet 8088
+
+# 生成性能分析报告
+$ gprof ./weblet gmon.out > perf.txt
+$ cat perf.txt
+```
+
+### 任务6:优化 Web 服务器
+
+> [!tip] 优化方向 — 参见 [[18_程序代码优化]]
+
+**抑制调试输出**:将 `printf` 等调试输出替换为条件编译或日志级别控制。
+
+```c
+/* 优化前: 每个请求都输出调试信息 */
+void process_trans(int fd, int hit) {
+ printf("Request %d: processing...\n", hit);
+ // ...
+}
+
+/* 优化后: 仅在DEBUG模式下输出 */
+#ifdef DEBUG
+#define DBG_LOG(fmt, ...) printf(fmt, ##__VA_ARGS__)
+#else
+#define DBG_LOG(fmt, ...) /* nothing */
+#endif
+
+void process_trans(int fd, int hit) {
+ DBG_LOG("Request %d: processing...\n", hit);
+ // ...
+}
+```
+
+```bash
+# 编译优化版本 (关闭调试输出)
+$ gcc -O2 -o weblet_opt weblet.c common.c
+
+# 重新进行性能测试,对比优化前后结果
+$ http_load -parallel 10 -fetches 50 urls
+```
+
+### 任务7:优化服务器部署(跨节点测试)
+
+```bash
+# 服务器端 (节点A: 192.168.1.100)
+$ ./weblet 8088
+
+# 客户端 (节点B: 192.168.1.200)
+# 修改urls文件中的地址
+$ cat urls
+http://192.168.1.100:8088/index.html
+http://192.168.1.100:8088/test.html
+
+# 跨节点压测
+$ http_load -parallel 10 -seconds 20 urls
+
+# 使用ab进行压测
+$ ab -n 1000 -c 100 http://192.168.1.100:8088/
+
+# 使用wrk进行压测
+$ wrk -t4 -c100 -d10s http://192.168.1.100:8088/
+```
+
+---
+
+## 八、性能测试命令汇总
+
+```bash
+# ===== http_load =====
+http_load -parallel 5 -fetches 50 urls # 并行5,50次请求
+http_load -parallel 10 -fetches 50 urls # 并行10,50次请求
+http_load -parallel 5 -seconds 20 urls # 并行5,持续20秒
+http_load -parallel 10 -seconds 20 urls # 并行10,持续20秒
+
+# ===== vmstat =====
+vmstat 2 10 # 每2秒采样,共10次
+
+# ===== iostat =====
+iostat -k 2 10 # KB为单位,每2秒采样
+
+# ===== gprof =====
+gprof ./weblet gmon.out > perf.txt # 生成函数调用分析
+
+# ===== ab (Apache Bench) =====
+ab -n 1000 -c 100 http://127.0.0.1:8080/ # 1000请求,并发100
+
+# ===== wrk =====
+wrk -t4 -c100 -d10s http://127.0.0.1:8088/ # 4线程,并发100,持续10秒
+```
+
+---
+
+## 九、Makefile 示例
+
+```makefile
+CC = gcc
+CFLAGS = -Wall -O2
+DEBUG_CFLAGS = -Wall -g -pg -DDEBUG
+
+all: weblet togglec togglesi webclient
+
+weblet: weblet.c common.c common.h
+ $(CC) $(CFLAGS) -o weblet weblet.c common.c
+
+togglec: togglec.c common.c common.h
+ $(CC) $(CFLAGS) -o togglec togglec.c common.c
+
+togglesi: togglesi.c common.c common.h
+ $(CC) $(CFLAGS) -o togglesi togglesi.c common.c
+
+webclient: webclient.c common.c common.h
+ $(CC) $(CFLAGS) -o webclient webclient.c common.c
+
+debug: weblet.c common.c common.h
+ $(CC) $(DEBUG_CFLAGS) -o weblet_debug weblet.c common.c
+
+clean:
+ rm -f weblet togglec togglesi webclient weblet_debug gmon.out perf.txt
+```
+
+---
+
+## 十、实验结果与分析
+
+### 10.1 性能对比记录表
+
+| 测试条件 | 并行度 | fetches/sec | bytes/sec | 备注 |
+|----------|--------|-------------|-----------|------|
+| 原始版本 | 5 | - | - | |
+| 原始版本 | 10 | - | - | |
+| 优化版本(关闭调试) | 5 | - | - | |
+| 优化版本(关闭调试) | 10 | - | - | |
+| 跨节点测试 | 10 | - | - | |
+
+### 10.2 gprof 分析要点
+
+> [!abstract] 关键发现
+> 1. 识别 CPU 时间占比最高的函数
+> 2. 分析系统调用(`read`、`write`、`accept`)的耗时
+> 3. 对比优化前后函数调用次数和耗时变化
+
+### 10.3 分析与思考
+
+1. **迭代式服务器的瓶颈**:单进程模型下,请求串行处理,并发性能受限。改进方案可参考 [[10_并发服务器]] 中的多进程 fork 模型、多线程 pthread 模型。
+
+2. **调试输出对性能的影响**:频繁的 `printf` 会触发系统调用和缓冲区刷新,在高并发场景下成为性能瓶颈。
+
+3. **网络传输 vs 本地测试**:跨节点测试引入了网络延迟,更接近真实场景,但本地测试更能反映服务器本身的处理能力。
+
+---
+
+## 十一、知识点总结
+
+```mermaid
+
+mindmap
+ root((Web服务器实现))
+ 网络编程基础
+ Socket API
+ socket/bind/listen/accept
+ connect/read/write
+ RIO函数库
+ 健壮的I/O操作
+ 带缓冲读取
+ 服务器架构
+ 迭代式服务器
+ 单进程串行处理
+ 简单但并发性差
+ 并发式服务器
+ 多进程fork模型
+ 多线程pthread模型
+ HTTP协议
+ 请求行解析
+ GET / URI / HTTP/1.1
+ MIME类型
+ Content-Type头部
+ 静态/动态内容
+ 文件服务 vs CGI
+ 性能测试
+ 压测工具
+ http_load
+ ab / wrk
+ 系统监控
+ vmstat / iostat
+ 代码分析
+ gprof 函数级分析
+
+
+
+```
+
+---
+
+## 十二、课后思考
+
+1. 如何将迭代式 Web 服务器改造为[[10_并发服务器|并发服务器]]?比较 `fork`、`pthread`、`select`/`epoll` 等方案的优劣。
+2. RIO 函数库解决了标准 I/O 的哪些问题?为什么不能直接使用 `read()` / `write()`?
+3. 如何通过 [[18_程序代码优化|代码优化]] 进一步提升 Web 服务器性能?(减少系统调用、零拷贝、内存映射等)
+4. 试分析 `gprof` 输出中各函数的调用关系和时间占比,找出性能热点。
+
+---
+
+> [!quote] 参考资料
+> - 《深入理解计算机系统》(CSAPP) 第11章:网络编程
+> - 《Unix网络编程》(UNP) 卷1:套接字联网API
+> - man pages: `socket(2)`, `bind(2)`, `listen(2)`, `accept(2)`, `connect(2)`
diff --git a/操作系统/操作系统实践/实践02_多进程多线程服务器.md b/操作系统/操作系统实践/实践02_多进程多线程服务器.md
new file mode 100644
index 0000000..e89ba0e
--- /dev/null
+++ b/操作系统/操作系统实践/实践02_多进程多线程服务器.md
@@ -0,0 +1,628 @@
+# 实践02:Web服务器的多进程与多线程模型实现
+
+> [!info] 实验信息
+> **课程**:操作系统实践
+> **实验名称**:实验二 — Web服务器的多进程与多线程模型实现
+> **前置实验**:[[实践01_Web服务器初步实现]]
+
+## 实验目的
+
+1. 将 Web 服务器改造为多进程、多线程、预线程、线程池四种版本
+2. 掌握并行网络服务器的设计方法
+3. 使用 `http_load` 进行性能测试与对比分析
+4. 理解不同并发模型的优劣及适用场景
+
+> [!tip] 关联理论课程
+> - [[06_进程控制]] — `fork`、信号处理、僵尸进程回收
+> - [[07_多线程编程]] — `pthread_create`、线程同步
+> - [[10_并发服务器]] — 并发模型概述与对比
+
+---
+
+## 源代码文件清单
+
+> [!note] 本次实验新增文件(在实验一基础上)
+> | 文件 | 说明 |
+> |------|------|
+> | `togglesp.c` | 多进程 toggle 服务器 |
+> | `togglest.c` | 多线程 toggle 服务器 |
+> | `togglest_pre.c` | 预线程 toggle 服务器 |
+> | `togglest_pool.c` | 线程池 toggle 服务器 |
+
+---
+
+## 一、四种并发模型总览
+
+### 模型架构图
+
+```mermaid
+graph TD
+ subgraph "模型一:多进程 Process-per-Connection"
+ C1[客户端 1] -->|accept| S1[主进程]
+ S1 -->|fork| P1[子进程 1]
+ S1 -->|fork| P2[子进程 2]
+ S1 -->|fork| P3[子进程 3]
+ P1 --> R1[处理请求]
+ P2 --> R2[处理请求]
+ P3 --> R3[处理请求]
+ end
+```
+
+```mermaid
+graph TD
+ subgraph "模型二:多线程 Thread-per-Connection"
+
+ C2[客户端] -->|accept| SM[主线程]
+ SM -->|pthread_create| T1[线程 1]
+ SM -->|pthread_create| T2[线程 2]
+ SM -->|pthread_create| T3[线程 3]
+ T1 --> H1[处理请求]
+ T2 --> H2[处理请求]
+ T3 --> H3[处理请求]
+
+ end
+```
+
+```mermaid
+graph TD
+ subgraph "模型三:预线程 Pre-threaded"
+ C3[客户端] -->|accept| PM[主线程]
+ PM -->|放入任务队列| Q[(任务队列)]
+ Q -->|取任务| W1[工作线程 1]
+ Q -->|取任务| W2[工作线程 2]
+ Q -->|取任务| W3[工作线程 3]
+ Q -->|取任务| W4[工作线程 N]
+ end
+```
+
+```mermaid
+graph TD
+ subgraph "模型四:线程池 Thread Pool"
+ C4[客户端] -->|submit| POOL[线程池管理器]
+ POOL -->|dispatch| Q2[(任务队列)]
+ Q2 -->|worker| WT1[工作线程 1]
+ Q2 -->|worker| WT2[工作线程 2]
+ Q2 -->|worker| WT3[工作线程 N]
+ WT1 -->|完成| POOL
+ WT2 -->|完成| POOL
+ end
+```
+
+### 四种模型对比
+
+| 模型 | 源文件 | 创建时机 | 优点 | 缺点 |
+|------|--------|----------|------|------|
+| 多进程 | `togglesp.c` | 按需 `fork` | 简单、进程隔离 | `fork` 开销大 |
+| 多线程 | `togglest.c` | 按需 `pthread_create` | 比 `fork` 轻量 | 线程创建仍有开销 |
+| 预线程 | `togglest_pre.c` | 启动时创建线程池 | 无运行时创建开销 | 需要任务队列同步 |
+| 线程池 | `togglest_pool.c` | 启动时创建线程池 | 最优性能、可复用 | 实现复杂度最高 |
+
+---
+
+## 二、任务一:验证 Toggle 服务器的四种版本
+
+### 2.1 编译
+
+```bash
+# 多进程版本
+gcc -o togglesp togglesp.c csapp.c -lpthread
+
+# 多线程版本
+gcc -o togglest togglest.c csapp.c -lpthread
+
+# 预线程版本
+gcc -o togglest_pre togglest_pre.c csapp.c -lpthread
+
+# 线程池版本
+gcc -o togglest_pool togglest_pool.c csapp.c -lpthread
+```
+
+### 2.2 分别运行与测试
+
+```bash
+# ---- 测试多进程版本 ----
+./togglesp 8080 &
+./togglec localhost 8080
+# 输入: Hello World
+# 输出: hELLO wORLD
+
+# ---- 测试多线程版本 ----
+./togglest 8081 &
+./togglec localhost 8081
+
+# ---- 测试预线程版本 ----
+./togglest_pre 8082 &
+./togglec localhost 8082
+
+# ---- 测试线程池版本 ----
+./togglest_pool 8083 &
+./togglec localhost 8083
+```
+
+> [!warning] 注意
+> 测试前确保端口未被占用,可用 `lsof -i:8080` 或 `netstat -tlnp` 检查。
+
+---
+
+## 三、多进程模型详解 — togglesp.c
+
+### 3.1 整体流程
+
+```mermaid
+sequenceDiagram
+ participant Main as 主进程
+ participant Child as 子进程
+ participant Client as 客户端
+
+ Main->>Main: Signal(SIGCHLD, handler)
+ Main->>Main: listen_sock = open_listen_sock(port)
+ loop 持续接受连接
+ Main->>Main: conn_sock = accept(...)
+ Main->>Main: fork()
+ alt 子进程
+ Main->>Child: 进入子进程
+ Child->>Main: close(listen_sock)
+ Child->>Client: toggle(conn_sock, hit)
+ Child->>Child: exit(0)
+ else 父进程
+ Main->>Main: close(conn_sock)
+ end
+ end
+```
+
+### 3.2 核心代码解析
+
+#### 信号处理:回收僵尸进程
+
+```c
+void sigchld_handler(int sig) {
+ while (waitpid(-1, 0, WNOHANG) > 0);
+ // WNOHANG: 非阻塞,避免信号处理函数中阻塞
+ // while 循环: 一次回收所有已终止的子进程(防止信号丢失)
+}
+```
+
+> [!important] 为什么用 `while` 循环?
+> Unix 信号不排队。如果两个子进程几乎同时终止,可能只收到一次 `SIGCHLD`。循环调用 `waitpid` 确保所有僵尸进程都被回收。
+
+#### toggle 函数:大小写转换
+
+```c
+void toggle(int conn_sock, int hit) {
+ int n;
+ char buf[MAXLINE];
+
+ while ((n = recv(conn_sock, buf, MAXLINE, 0)) > 0) {
+ // 逐字节进行大小写转换
+ for (int i = 0; i < n; i++) {
+ if (isupper(buf[i]))
+ buf[i] = tolower(buf[i]);
+ else if (islower(buf[i]))
+ buf[i] = toupper(buf[i]);
+ }
+ send(conn_sock, buf, n, 0); // 原样发回转换后的数据
+ }
+}
+```
+
+#### 主循环:fork 并发处理
+
+```c
+int main(int argc, char **argv) {
+ int listen_sock, conn_sock, hit;
+ socklen_t clientlen;
+ struct sockaddr_storage clientaddr;
+
+ Signal(SIGCHLD, sigchld_handler); // 注册 SIGCHLD 处理函数
+ listen_sock = open_listen_sock(argv[1]); // 创建监听套接字
+
+ for (hit = 1; ; hit++) {
+ clientlen = sizeof(clientaddr);
+ conn_sock = accept(listen_sock,
+ (SA *)&clientaddr, &clientlen);
+
+ if (Fork() == 0) { // 子进程
+ close(listen_sock); // 关闭监听套接字(子进程不需要)
+ toggle(conn_sock, hit); // 处理请求
+ close(conn_sock); // 关闭连接套接字
+ exit(0); // 子进程退出
+ }
+ close(conn_sock); // 父进程关闭连接套接字
+ }
+}
+```
+
+> [!note] 父子进程的套接字处理
+> - **父进程**:`close(conn_sock)` — 父进程不处理该连接
+> - **子进程**:`close(listen_sock)` — 子进程不接受新连接
+> - 这是多进程服务器的标准模式,避免文件描述符泄漏
+
+### 3.3 多进程模型的优缺点
+
+```
+优点 缺点
++----------------------------------+----------------------------------+
+| 进程间地址空间隔离 | fork() 系统调用开销大 |
+| 一个进程崩溃不影响其他进程 | 每个进程占用独立内存空间 |
+| 编程模型简单直观 | 进程数受系统限制 |
+| 天然线程安全(无共享数据) | 上下文切换开销大 |
++----------------------------------+----------------------------------+
+```
+
+---
+
+## 四、多线程模型详解 — togglest.c
+
+### 4.1 核心结构
+
+```c
+// 线程参数结构体
+typedef struct {
+ int conn_sock;
+ int hit;
+} thread_args_t;
+
+void *thread_toggle(void *vargp) {
+ thread_args_t *args = (thread_args_t *)vargp;
+ int conn_sock = args->conn_sock;
+ int hit = args->hit;
+ Free(vargp); // 释放参数内存
+
+ toggle(conn_sock, hit); // 处理请求(复用 toggle 函数)
+ Close(conn_sock); // 关闭连接
+ return NULL;
+}
+
+int main(int argc, char **argv) {
+ listen_sock = open_listen_sock(port);
+
+ for (hit = 1; ; hit++) {
+ conn_sock = accept(listen_sock, ...);
+
+ // 为每个连接分配参数
+ thread_args_t *args = Malloc(sizeof(thread_args_t));
+ args->conn_sock = conn_sock;
+ args->hit = hit;
+
+ // 创建分离线程(自动回收,无需 join)
+ Pthread_create(&tid, NULL, thread_toggle, args);
+ }
+}
+```
+
+> [!tip] 为什么要用 `pthread_detach`?
+> 如果不分离线程,线程终止后其资源不会自动释放,造成资源泄漏(类似僵尸进程)。使用 `pthread_detach` 或 `pthread_create` 后立即 `pthread_detach` 可避免此问题。
+
+### 4.2 多线程 vs 多进程
+
+```mermaid
+graph LR
+ subgraph 多进程
+ A1[主进程] -->|fork| A2[子进程]
+ A2 -->|独立地址空间| A3[处理请求]
+ end
+
+ subgraph 多线程
+ B1[主线程] -->|pthread_create| B2[新线程]
+ B2 -->|共享地址空间| B3[处理请求]
+ end
+
+ style A1 fill:#ffcdd2
+ style A2 fill:#ffcdd2
+ style A3 fill:#ffcdd2
+ style B1 fill:#c8e6c9
+ style B2 fill:#c8e6c9
+ style B3 fill:#c8e6c9
+```
+
+---
+
+## 五、预线程模型详解 — togglest_pre.c
+
+### 5.1 设计思想
+
+预线程模型在服务器启动时就创建固定数量的工作线程,所有线程共享一个任务队列,形成**生产者-消费者**模型。
+
+```mermaid
+graph LR
+ P[主线程/生产者] -->|sbuf_insert| Q[(共享任务队列
conn_sock)]
+ Q -->|sbuf_remove| W1[工作线程 1]
+ Q -->|sbuf_remove| W2[工作线程 2]
+ Q -->|sbuf_remove| W3[工作线程 3]
+ Q -->|sbuf_remove| WN[工作线程 N]
+
+ style P fill:#bbdefb
+ style Q fill:#fff9c4
+ style W1 fill:#c8e6c9
+ style W2 fill:#c8e6c9
+ style W3 fill:#c8e6c9
+ style WN fill:#c8e6c9
+```
+
+### 5.2 核心代码解析
+
+#### Sbuf:带信号量的共享缓冲区
+
+```c
+typedef struct {
+ int *buf; // 缓冲区数组
+ int n; // 缓冲区容量
+ int front; // 队头索引
+ int rear; // 队尾索引
+ sem_t mutex; // 互斥信号量
+ sem_t slots; // 空槽数量信号量
+ sem_t items; // 已有项目数信号量
+} sbuf_t;
+
+void sbuf_insert(sbuf_t *sp, int item) {
+ P(&sp->slots); // 等待空槽
+ P(&sp->mutex); // 加锁
+ sp->buf[(++sp->rear) % sp->n] = item; // 入队
+ V(&sp->mutex); // 解锁
+ V(&sp->items); // 增加项目数
+}
+
+int sbuf_remove(sbuf_t *sp) {
+ P(&sp->items); // 等待有项目
+ P(&sp->mutex); // 加锁
+ int item = sp->buf[(++sp->front) % sp->n]; // 出队
+ V(&sp->mutex); // 解锁
+ V(&sp->slots); // 增加空槽数
+ return item;
+}
+```
+
+> [!important] 信号量的顺序不能颠倒!
+> `sbuf_insert` 中先 `P(slots)` 再 `P(mutex)`,如果反过来,可能导致死锁:线程持有 mutex 但等待 slots,而其他线程无法获取 mutex 来释放 slots。
+
+#### 工作线程函数
+
+```c
+void *thread_toggle(void *vargp) {
+ sbuf_t *sp = (sbuf_t *)vargp;
+ Pthread_detach(pthread_self());
+
+ while (1) {
+ int conn_sock = sbuf_remove(sp); // 阻塞等待任务
+ toggle(conn_sock, -1); // 处理请求
+ Close(conn_sock); // 关闭连接
+ }
+ return NULL;
+}
+
+int main(int argc, char **argv) {
+ sbuf_t sbuf;
+ sbuf_init(&sbuf, SBUFSIZE); // 初始化缓冲区
+
+ // 启动时创建 N 个工作线程
+ for (int i = 0; i < NTHREADS; i++)
+ Pthread_create(&tid, NULL, thread_toggle, &sbuf);
+
+ listen_sock = open_listen_sock(port);
+
+ while (1) {
+ conn_sock = accept(listen_sock, ...);
+ sbuf_insert(&sbuf, conn_sock); // 主线程生产
+ }
+}
+```
+
+### 5.3 预线程模型的优势
+
+```
+传统模型(按需创建) 预线程模型(预先创建)
+accept --> fork/create accept --> 入队
+ | |
+ +--> [创建开销] --> 处理 +--> [零开销] --> 工作线程直接处理
+```
+
+- **消除运行时创建开销**:线程在启动时一次性创建完毕
+- **控制并发度**:线程数固定,避免系统过载
+- **生产者-消费者解耦**:主线程只负责 accept,工作线程只负责处理
+
+---
+
+## 六、任务二~四:将 Toggle 服务器改造为 Weblet 服务器
+
+### 6.1 改造思路
+
+Toggle 服务器的核心逻辑是**大小写转换**,Weblet 服务器的核心逻辑是**HTTP 请求解析与响应**。改造的关键在于将 `toggle()` 函数替换为 HTTP 处理逻辑。
+
+```mermaid
+graph TD
+ A[Toggle 服务器] -->|替换核心处理函数| B[Weblet 服务器]
+
+ A --> A1["toggle(): 大小写转换"]
+ B --> B1["doit(): HTTP 请求处理"]
+
+ B1 --> B2[解析请求行 GET /path HTTP/1.1]
+ B1 --> B3[解析请求头]
+ B1 --> B4[构建响应: 静态/动态内容]
+ B1 --> B5[发送响应给客户端]
+```
+
+### 6.2 多进程 Weblet
+
+```c
+// 仿照 togglesp.c 的结构
+void sigchld_handler(int sig) {
+ while (waitpid(-1, 0, WNOHANG) > 0);
+}
+
+int main(int argc, char **argv) {
+ Signal(SIGCHLD, sigchld_handler);
+ listen_sock = open_listen_sock(port);
+
+ for (hit = 1; ; hit++) {
+ conn_sock = accept(listen_sock, ...);
+
+ if (Fork() == 0) {
+ close(listen_sock);
+ doit(conn_sock, hit); // 替换为 HTTP 处理
+ close(conn_sock);
+ exit(0);
+ }
+ close(conn_sock);
+ }
+}
+```
+
+### 6.3 多线程 Weblet
+
+```c
+// 仿照 togglest.c 的结构
+void *thread_doit(void *vargp) {
+ int conn_sock = *((int *)vargp);
+ Free(vargp);
+ Pthread_detach(pthread_self());
+
+ doit(conn_sock, -1); // HTTP 处理
+ Close(conn_sock);
+ return NULL;
+}
+
+int main(int argc, char **argv) {
+ listen_sock = open_listen_sock(port);
+
+ while (1) {
+ clientlen = sizeof(clientaddr);
+ conn_sock = accept(listen_sock, ...);
+
+ int *fdp = Malloc(sizeof(int));
+ *fdp = conn_sock;
+ Pthread_create(&tid, NULL, thread_doit, fdp);
+ }
+}
+```
+
+### 6.4 预线程 Weblet
+
+```c
+// 仿照 togglest_pre.c 的结构
+void *thread_doit(void *vargp) {
+ sbuf_t *sp = (sbuf_t *)vargp;
+ Pthread_detach(pthread_self());
+
+ while (1) {
+ int conn_sock = sbuf_remove(sp);
+ doit(conn_sock, -1); // HTTP 处理
+ Close(conn_sock);
+ }
+}
+
+int main(int argc, char **argv) {
+ sbuf_t sbuf;
+ sbuf_init(&sbuf, SBUFSIZE);
+
+ for (int i = 0; i < NTHREADS; i++)
+ Pthread_create(&tid, NULL, thread_doit, &sbuf);
+
+ listen_sock = open_listen_sock(port);
+
+ while (1) {
+ conn_sock = accept(listen_sock, ...);
+ sbuf_insert(&sbuf, conn_sock);
+ }
+}
+```
+
+---
+
+## 七、任务五:性能对比测试(http_load)
+
+### 7.1 测试方法
+
+```bash
+# 生成 URL 文件
+echo "http://localhost:8080/index.html" > urls.txt
+
+# 使用 http_load 进行压力测试
+# -p 并发数,-s 测试时间(秒)
+http_load -p 10 -s 10 urls.txt # 10 并发,持续 10 秒
+http_load -p 50 -s 10 urls.txt # 50 并发,持续 10 秒
+http_load -p 100 -s 10 urls.txt # 100 并发,持续 10 秒
+http_load -p 200 -s 10 urls.txt # 200 并发,持续 10 秒
+```
+
+### 7.2 测试结果记录模板
+
+| 并发数 | 多进程 (req/s) | 多线程 (req/s) | 预线程 (req/s) | 线程池 (req/s) |
+|--------|---------------|---------------|---------------|---------------|
+| 10 | | | | |
+| 50 | | | | |
+| 100 | | | | |
+| 200 | | | | |
+
+### 7.3 预期结论
+
+> [!abstract] 性能排序
+> **线程池 >= 预线程 > 多线程 > 多进程**
+>
+> - **多进程**:`fork()` 开销随并发数增加显著上升
+> - **多线程**:`pthread_create` 比 `fork` 轻量,但仍有创建开销
+> - **预线程**:启动时创建线程池,运行时零创建开销
+> - **线程池**:在预线程基础上增加了线程复用和动态管理
+
+---
+
+## 八、关键知识点总结
+
+### fork 与 pthread_create 的对比
+
+| 特性 | fork (多进程) | pthread_create (多线程) |
+|------|--------------|------------------------|
+| 地址空间 | 复制(写时复制) | 共享 |
+| 创建开销 | 大(页表复制) | 较小 |
+| 通信方式 | 进程间通信(管道等) | 直接共享内存 |
+| 崩溃隔离 | 互相隔离 | 一个线程崩溃可能影响全部 |
+| 文件描述符 | 复制 | 共享 |
+| 信号处理 | 各进程独立 | 需要协调 |
+
+### 信号量在生产者-消费者中的应用
+
+```mermaid
+sequenceDiagram
+ participant P as 生产者(主线程)
+ participant M as mutex
+ participant S as slots 信号量
+ participant I as items 信号量
+ participant C as 消费者(工作线程)
+
+ P->>S: P(slots) — 等待空槽
+ P->>M: P(mutex) — 加锁
+ P->>P: 插入任务到队列
+ P->>M: V(mutex) — 解锁
+ P->>I: V(items) — 通知有新任务
+
+ C->>I: P(items) — 等待任务
+ C->>M: P(mutex) — 加锁
+ C->>C: 从队列取出任务
+ C->>M: V(mutex) — 解锁
+ C->>S: V(slots) — 释放空槽
+ C->>C: 处理请求
+```
+
+---
+
+## 常见问题
+
+> [!failure] 问题:多进程版本出现僵尸进程
+> **原因**:未正确处理 `SIGCHLD` 信号,或信号处理函数中未使用 `WNOHANG` 循环回收。
+> **解决**:确保 `sigchld_handler` 中使用 `while (waitpid(-1, 0, WNOHANG) > 0);`
+
+> [!failure] 问题:多线程版本出现段错误(Segmentation Fault)
+> **原因**:传递给线程的参数在主线程中被覆盖。
+> **解决**:为每个线程 `malloc` 独立的参数结构体,不要传递局部变量的地址。
+
+> [!failure] 问题:预线程版本程序卡死不响应
+> **原因**:信号量使用顺序错误导致死锁。
+> **解决**:`sbuf_insert` 中先 `P(slots)` 后 `P(mutex)`;`sbuf_remove` 中先 `P(items)` 后 `P(mutex)`。
+
+---
+
+## 扩展阅读
+
+- [[06_进程控制]] — fork、waitpid、信号处理详细原理
+- [[07_多线程编程]] — pthread 线程编程详解
+- [[10_并发服务器]] — 三种并发模型的理论对比
+- [[实践01_Web服务器初步实现]] — 实验一基础 Web 服务器
diff --git a/操作系统/操作系统实践/实践03_线程池与业务分割.md b/操作系统/操作系统实践/实践03_线程池与业务分割.md
new file mode 100644
index 0000000..4e47f2e
--- /dev/null
+++ b/操作系统/操作系统实践/实践03_线程池与业务分割.md
@@ -0,0 +1,675 @@
+# 实践03 Web服务器的线程池和业务分割模型
+
+> [!abstract] 实验概要
+> - **实验名称**: 实验三. Web服务器的线程池和业务分割模型
+> - **实验目的**: 将webserver改造为预线程版本和业务分割模型,并进行性能测试,掌握并行网络服务器的设计、性能测试和优化方法
+> - **前置知识**: [[07_多线程编程]]、[[10_并发服务器]]、[[实践02_多进程多线程服务器]]
+> - **核心概念**: 线程池、生产者-消费者模型、业务分割、性能测试
+
+---
+
+## 一、实验目的
+
+1. 理解预线程化(prethreading)服务器的工作原理
+2. 掌握线程池的设计与实现方法
+3. 理解业务分割模型的架构思想
+4. 学会使用 `http_load` 进行服务器性能测试
+5. 掌握使用 `vmstat`、`iostat`、`gprof` 等工具监测系统性能
+6. 通过对比分析,理解不同并发模型的性能差异
+
+---
+
+## 二、涉及知识点
+
+- POSIX 线程池设计(详见 [[07_多线程编程]])
+- 生产者-消费者模型与信号量同步
+- 预线程化服务器模型(详见 [[10_并发服务器]])
+- 业务分割(Pipeline)并行模型
+- 消息队列设计与实现
+- 性能测试工具:`http_load`、`vmstat`、`iostat`、`gprof`
+- 并行服务器的性能瓶颈分析与优化
+
+---
+
+## 三、源代码文件清单
+
+| 文件名 | 说明 |
+|--------|------|
+| `taskline.c` / `taskline.h` | 预线程服务器任务管理程序 |
+| `pool.c` / `pool.h` | 线程池服务器的线程管理和任务管理程序 |
+| `urls` | `http_load` 测试 URL 列表 |
+
+> [!tip] 编译命令
+> 使用调试模式编译:
+> ```bash
+> make M="-DDEBUG" rebuild
+> ```
+
+---
+
+## 四、实验任务
+
+### 任务一:准备工作
+
+将源代码复制到 Ubuntu 环境并编译:
+
+```bash
+# 复制源代码到工作目录
+cp -r /path/to/source/* ./
+
+# 使用调试模式编译
+make M="-DDEBUG" rebuild
+```
+
+> [!warning] 注意事项
+> 确保系统已安装 `http_load` 工具,如未安装:
+> ```bash
+> sudo apt-get install http-load
+> ```
+
+### 任务二:阅读和理解线程池实现
+
+阅读并理解 `togglest_pool.c` 的线程池实现,编译运行验证其正确性。
+
+#### 线程池核心数据结构
+
+```c
+/* pool.h - 线程池定义 */
+typedef struct {
+ pthread_t *threads; /* 线程数组 */
+ int thread_count; /* 线程数量 */
+ int *task_queue; /* 任务队列(socket fd) */
+ int queue_size; /* 队列容量 */
+ int front; /* 队头指针 */
+ int rear; /* 队尾指针 */
+ int count; /* 当前队列中任务数 */
+ pthread_mutex_t mutex; /* 互斥锁 */
+ sem_t slots; /* 空闲槽位信号量 */
+ sem_t items; /* 可用任务信号量 */
+ int shutdown; /* 关闭标志 */
+} thread_pool_t;
+```
+
+#### 线程池核心操作
+
+```c
+/* pool.c - 线程池操作实现 */
+
+/* 初始化线程池 */
+void pool_init(thread_pool_t *pool, int threads, int queue_size) {
+ pool->threads = (pthread_t *)malloc(threads * sizeof(pthread_t));
+ pool->task_queue = (int *)malloc(queue_size * sizeof(int));
+ pool->thread_count = threads;
+ pool->queue_size = queue_size;
+ pool->front = pool->rear = pool->count = 0;
+ pool->shutdown = 0;
+
+ pthread_mutex_init(&pool->mutex, NULL);
+ sem_init(&pool->slots, 0, queue_size);
+ sem_init(&pool->items, 0, 0);
+
+ /* 预创建所有工作线程 */
+ for (int i = 0; i < threads; i++) {
+ pthread_create(&pool->threads[i], NULL, worker, (void *)pool);
+ }
+}
+
+/* 向任务队列添加任务(生产者) */
+void pool_enqueue(thread_pool_t *pool, int conn_fd) {
+ sem_wait(&pool->slots); /* 等待空闲槽位 */
+ pthread_mutex_lock(&pool->mutex); /* 进入临界区 */
+ pool->task_queue[pool->rear] = conn_fd;
+ pool->rear = (pool->rear + 1) % pool->queue_size;
+ pool->count++;
+ pthread_mutex_unlock(&pool->mutex);
+ sem_post(&pool->items); /* 通知有新任务 */
+}
+
+/* 从任务队列取出任务(消费者) */
+int pool_dequeue(thread_pool_t *pool) {
+ sem_wait(&pool->items); /* 等待可用任务 */
+ pthread_mutex_lock(&pool->mutex); /* 进入临界区 */
+ int conn_fd = pool->task_queue[pool->front];
+ pool->front = (pool->front + 1) % pool->queue_size;
+ pool->count--;
+ pthread_mutex_unlock(&pool->mutex);
+ sem_post(&pool->slots); /* 释放槽位 */
+ return conn_fd;
+}
+
+/* 工作线程主函数 */
+void *worker(void *arg) {
+ thread_pool_t *pool = (thread_pool_t *)arg;
+ while (1) {
+ int conn_fd = pool_dequeue(pool); /* 取出任务 */
+ if (conn_fd < 0) break; /* 收到关闭信号 */
+ handle_request(conn_fd); /* 处理 HTTP 请求 */
+ close(conn_fd); /* 关闭连接 */
+ }
+ return NULL;
+}
+```
+
+#### 线程池工作流程
+
+```
+主线程(Accept) --> 任务队列 --> 工作线程1 (处理请求)
+ [conn_fd] --> 工作线程2 (处理请求)
+ --> 工作线程N (处理请求)
+```
+
+> [!info] 生产者-消费者模型
+> 线程池本质上是一个 **多生产者-多消费者** 模型:
+> - **生产者**:主线程不断 `accept` 新连接,将 `conn_fd` 放入队列
+> - **消费者**:工作线程从队列取出 `conn_fd`,处理 HTTP 请求
+> - 信号量 `slots` 和 `items` 实现了线程间的同步
+>
+> 该模型在 [[07_多线程编程]] 中已有详细讨论,此处是其在网络服务器中的实际应用。
+
+### 任务三:实现 Web 服务器线程池版本
+
+仿照 `togglest_pool.c` 的设计,将 webserver 改造为线程池版本,并使用 `http_load` 测试性能。
+
+#### 编译与运行
+
+```bash
+# 编译线程池版 webserver
+make M="-DDEBUG" rebuild
+
+# 启动服务器
+./webserver 8080
+
+# 另一终端:http_load 性能测试
+http_load -parallel 5 -fetches 50 -seconds 20 urls
+```
+
+#### urls 文件示例
+
+```
+http://localhost:8080/index.html
+http://localhost:8080/test.html
+http://localhost:8080/image.jpg
+```
+
+> [!example] http_load 参数说明
+> | 参数 | 含义 |
+> |------|------|
+> | `-parallel N` | 并发连接数为 N |
+> | `-fetches N` | 总共发起 N 次请求 |
+> | `-seconds N` | 测试持续 N 秒 |
+>
+> 两参数同时指定时,先满足的条件生效。
+
+### 任务四:设计实现业务分割模型
+
+将 webserver 改造为业务分割模型,将 HTTP 请求处理拆分为三个阶段,每个阶段由独立的线程池负责。
+
+#### 业务分割架构图
+
+```mermaid
+graph LR
+ C[客户端请求] --> A[read msg
threadpool]
+ A -->|文件名 + socket| Q1[filename
queue]
+ Q1 --> B[read file
threadpool]
+ B -->|内容 + socket| Q2[msg
queue]
+ Q2 --> D[send msg
threadpool]
+ D --> R[客户端响应]
+
+ style A fill:#4CAF50,color:#fff
+ style B fill:#2196F3,color:#fff
+ style D fill:#FF9800,color:#fff
+ style Q1 fill:#E91E63,color:#fff
+ style Q2 fill:#9C27B0,color:#fff
+```
+
+#### 业务分割详细流程
+
+```mermaid
+graph TB
+ subgraph "阶段一:消息读取与解析"
+ A1[线程从 socket 读取 HTTP 请求] --> A2[解析请求行
GET /path HTTP/1.1]
+ A2 --> A3[提取文件名]
+ A3 --> A4[将 filename + conn_fd
加入 filename queue]
+ end
+
+ subgraph "阶段二:文件读取"
+ B1[线程从 filename queue
取出 filename + conn_fd] --> B2[打开并读取文件内容]
+ B2 --> B3[构造 HTTP 响应]
+ B3 --> B4[将 response + conn_fd
加入 msg queue]
+ end
+
+ subgraph "阶段三:响应发送"
+ C1[线程从 msg queue
取出 response + conn_fd] --> C2[通过 socket
发送 HTTP 响应]
+ C2 --> C3[关闭连接]
+ end
+
+ A4 --> B1
+ B4 --> C1
+
+ style A1 fill:#4CAF50,color:#fff
+ style B1 fill:#2196F3,color:#fff
+ style C1 fill:#FF9800,color:#fff
+```
+
+#### 消息传递时序图
+
+```mermaid
+sequenceDiagram
+ participant Client as 客户端
+ participant RM as read msg
threadpool
+ participant FQ as filename
queue
+ participant RF as read file
threadpool
+ participant MQ as msg
queue
+ participant SM as send msg
threadpool
+
+ Client->>RM: HTTP 请求 (socket)
+ activate RM
+ RM->>RM: 读取 socket 数据
+ RM->>RM: 解析 HTTP 请求
提取 filename
+ RM->>FQ: enqueue(filename, socket)
+ deactivate RM
+
+ FQ->>RF: dequeue(filename, socket)
+ activate RF
+ RF->>RF: 打开文件
+ RF->>RF: 读取文件内容
+ RF->>RF: 构造 HTTP 响应
+ RF->>MQ: enqueue(response, socket)
+ deactivate RF
+
+ MQ->>SM: dequeue(response, socket)
+ activate SM
+ SM->>Client: 发送 HTTP 响应
+ SM->>SM: 关闭连接
+ deactivate SM
+```
+
+#### 三个线程池的职责
+
+> [!note] 线程池一:read msg threadpool
+> - **职责**: 从 socket 读取 HTTP 请求消息并解析
+> - **输入**: 客户端的 socket 连接
+> - **处理**: 调用 `recv()` 读取数据,解析请求行获取文件名
+> - **输出**: 将 `filename + conn_fd` 加入 filename queue
+> - **关键点**: I/O 密集型操作,需要处理不完整读取
+
+> [!note] 线程池二:read file threadpool
+> - **职责**: 根据文件名读取文件内容
+> - **输入**: 从 filename queue 取出的 `filename + conn_fd`
+> - **处理**: 打开文件、读取内容、构造 HTTP 响应头和正文
+> - **输出**: 将 `response + conn_fd` 加入 msg queue
+> - **关键点**: 磁盘 I/O 密集型操作,是性能瓶颈之一
+
+> [!note] 线程池三:send msg threadpool
+> - **职责**: 将 HTTP 响应发送回客户端
+> - **输入**: 从 msg queue 取出的 `response + conn_fd`
+> - **处理**: 调用 `send()` 发送响应数据
+> - **输出**: 关闭 socket 连接
+> - **关键点**: 网络 I/O 密集型操作
+
+#### 两个消息队列
+
+> [!important] 消息队列设计
+> 消息队列是线程池之间通信的桥梁,采用生产者-消费者模型实现:
+>
+> **filename queue**:
+> - 生产者: read msg threadpool
+> - 消费者: read file threadpool
+> - 数据: `{char filename[256], int conn_fd}`
+>
+> **msg queue**:
+> - 生产者: read file threadpool
+> - 消费者: send msg threadpool
+> - 数据: `{char *response, int length, int conn_fd}`
+
+#### 消息队列核心实现
+
+```c
+/* 消息队列数据结构 */
+typedef struct {
+ int conn_fd;
+ char filename[256]; /* filename queue 使用 */
+ char *response; /* msg queue 使用 */
+ int response_len; /* 响应长度 */
+} queue_item_t;
+
+typedef struct {
+ queue_item_t *items;
+ int capacity;
+ int front;
+ int rear;
+ int count;
+ pthread_mutex_t mutex;
+ sem_t slots;
+ sem_t items_sem;
+} message_queue_t;
+
+/* 初始化消息队列 */
+void mq_init(message_queue_t *mq, int capacity) {
+ mq->items = (queue_item_t *)calloc(capacity, sizeof(queue_item_t));
+ mq->capacity = capacity;
+ mq->front = mq->rear = mq->count = 0;
+ pthread_mutex_init(&mq->mutex, NULL);
+ sem_init(&mq->slots, 0, capacity);
+ sem_init(&mq->items_sem, 0, 0);
+}
+
+/* 入队操作 */
+void mq_enqueue(message_queue_t *mq, queue_item_t *item) {
+ sem_wait(&mq->slots);
+ pthread_mutex_lock(&mq->mutex);
+ mq->items[mq->rear] = *item;
+ mq->rear = (mq->rear + 1) % mq->capacity;
+ mq->count++;
+ pthread_mutex_unlock(&mq->mutex);
+ sem_post(&mq->items_sem);
+}
+
+/* 出队操作 */
+void mq_dequeue(message_queue_t *mq, queue_item_t *item) {
+ sem_wait(&mq->items_sem);
+ pthread_mutex_lock(&mq->mutex);
+ *item = mq->items[mq->front];
+ mq->front = (mq->front + 1) % mq->capacity;
+ mq->count--;
+ pthread_mutex_unlock(&mq->mutex);
+ sem_post(&mq->slots);
+}
+```
+
+#### 业务分割服务器主框架
+
+```c
+/* taskline.c - 业务分割 webserver 主框架 */
+#include "taskline.h"
+#include "pool.h"
+
+#define READ_MSG_THREADS 4 /* 读消息线程池大小 */
+#define READ_FILE_THREADS 4 /* 读文件线程池大小 */
+#define SEND_MSG_THREADS 4 /* 发送消息线程池大小 */
+#define QUEUE_CAPACITY 64 /* 消息队列容量 */
+
+/* 两个消息队列 */
+message_queue_t filename_queue; /* 文件名队列 */
+message_queue_t msg_queue; /* 响应消息队列 */
+
+/* 阶段一:读取并解析 HTTP 请求 */
+void *read_msg_worker(void *arg) {
+ while (1) {
+ int conn_fd = pool_dequeue(&read_msg_pool);
+
+ char buf[8192];
+ int n = recv(conn_fd, buf, sizeof(buf) - 1, 0);
+ if (n <= 0) { close(conn_fd); continue; }
+ buf[n] = '\0';
+
+ /* 解析 HTTP 请求 */
+ char method[16], path[256], version[16];
+ sscanf(buf, "%s %s %s", method, path, version);
+
+ /* 构造文件路径 */
+ char filename[256];
+ snprintf(filename, sizeof(filename), ".%s", path);
+
+ /* 将 filename + conn_fd 加入 filename queue */
+ queue_item_t item;
+ item.conn_fd = conn_fd;
+ strncpy(item.filename, filename, sizeof(item.filename));
+ mq_enqueue(&filename_queue, &item);
+ }
+ return NULL;
+}
+
+/* 阶段二:读取文件内容 */
+void *read_file_worker(void *arg) {
+ while (1) {
+ queue_item_t item;
+ mq_dequeue(&filename_queue, &item);
+
+ /* 打开并读取文件 */
+ int fd = open(item.filename, O_RDONLY);
+ if (fd < 0) {
+ /* 文件不存在,构造 404 响应 */
+ item.response = build_404_response(&item.response_len);
+ } else {
+ /* 读取文件,构造 200 响应 */
+ item.response = build_200_response(fd, &item.response_len);
+ close(fd);
+ }
+
+ /* 将 response + conn_fd 加入 msg queue */
+ mq_enqueue(&msg_queue, &item);
+ }
+ return NULL;
+}
+
+/* 阶段三:发送 HTTP 响应 */
+void *send_msg_worker(void *arg) {
+ while (1) {
+ queue_item_t item;
+ mq_dequeue(&msg_queue, &item);
+
+ /* 发送响应 */
+ send(item.conn_fd, item.response, item.response_len, 0);
+ free(item.response);
+ close(item.conn_fd);
+ }
+ return NULL;
+}
+
+int main(int argc, char **argv) {
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s \n", argv[0]);
+ exit(1);
+ }
+
+ /* 初始化两个消息队列 */
+ mq_init(&filename_queue, QUEUE_CAPACITY);
+ mq_init(&msg_queue, QUEUE_CAPACITY);
+
+ /* 初始化三个线程池 */
+ pool_init(&read_msg_pool, READ_MSG_THREADS, QUEUE_CAPACITY);
+ pool_init(&read_file_pool, READ_FILE_THREADS, QUEUE_CAPACITY);
+ pool_init(&send_msg_pool, SEND_MSG_THREADS, QUEUE_CAPACITY);
+
+ /* 启动三个阶段的工作线程 */
+ pthread_t tid;
+ for (int i = 0; i < READ_MSG_THREADS; i++)
+ pthread_create(&tid, NULL, read_msg_worker, NULL);
+ for (int i = 0; i < READ_FILE_THREADS; i++)
+ pthread_create(&tid, NULL, read_file_worker, NULL);
+ for (int i = 0; i < SEND_MSG_THREADS; i++)
+ pthread_create(&tid, NULL, send_msg_worker, NULL);
+
+ /* 主线程接受连接 */
+ int listen_fd = open_listen_sock(atoi(argv[1]));
+ printf("业务分割 webserver 启动,端口 %s\n", argv[1]);
+
+ while (1) {
+ struct sockaddr_in cliaddr;
+ socklen_t clien = sizeof(cliaddr);
+ int conn_fd = accept(listen_fd,
+ (struct sockaddr *)&cliaddr, &clien);
+ if (conn_fd < 0) continue;
+
+ /* 将新连接放入 read msg 线程池的任务队列 */
+ pool_enqueue(&read_msg_pool, conn_fd);
+ }
+
+ return 0;
+}
+```
+
+> [!tip] 与 [[实践02_多进程多线程服务器]] 的对比
+> - 实践02 中每个请求由单个线程串行完成「读消息 -> 读文件 -> 发送响应」全部工作
+> - 本实验将三个阶段拆分到不同的线程池,实现了 **流水线并行**
+> - 当一个线程在发送响应时,其他线程已经在处理新请求的文件读取,提高了吞吐量
+
+---
+
+## 五、性能测试
+
+### 5.1 http_load 测试
+
+```bash
+# 低并发测试
+http_load -parallel 5 -fetches 50 -seconds 20 urls
+
+# 中并发测试
+http_load -parallel 20 -fetches 200 -seconds 20 urls
+
+# 高并发测试
+http_load -parallel 50 -fetches 500 -seconds 20 urls
+```
+
+> [!example] http_load 输出指标
+> | 指标 | 含义 |
+> |------|------|
+> | fetches | 成功完成的请求总数 |
+> | elapsed | 测试耗时(秒) |
+> | mean bytes/transfer | 平均每次传输字节数 |
+> | fetches/sec | 每秒完成的请求数(吞吐量) |
+> | msecs/connect | 平均连接建立时间 |
+> | msecs/first-response | 平均首字节响应时间 |
+> | HTTP response codes | 各状态码的出现次数 |
+
+### 5.2 系统性能监测
+
+#### vmstat 监控
+
+```bash
+# 每 2 秒采集一次,共 10 次
+vmstat 2 10
+```
+
+> [!info] vmstat 关键字段
+> | 字段 | 含义 |
+> |------|------|
+> | `r` | 运行队列中的进程数 |
+> | `b` | 阻塞等待 I/O 的进程数 |
+> | `us` | 用户态 CPU 占用百分比 |
+> | `sy` | 内核态 CPU 占用百分比 |
+> | `wa` | I/O 等待 CPU 时间百分比 |
+> | `free` | 空闲内存(KB) |
+> | `buff` | 缓冲区大小(KB) |
+
+#### iostat 监控
+
+```bash
+# 每 2 秒采集一次,共 10 次
+iostat -k 2 10
+```
+
+> [!info] iostat 关键字段
+> | 字段 | 含义 |
+> |------|------|
+> | `tps` | 每秒传输次数 |
+> | `kB_read/s` | 每秒读取数据量(KB) |
+> | `kB_wrtn/s` | 每秒写入数据量(KB) |
+> | `await` | 平均 I/O 等待时间(ms) |
+> | `util` | 设备利用率百分比 |
+
+#### gprof 性能分析
+
+```bash
+# 编译时加入性能分析选项
+gcc -pg -o webserver webserver.c pool.c taskline.c -lpthread
+
+# 运行服务器并进行测试
+./webserver 8080 &
+http_load -parallel 20 -fetches 200 -seconds 20 urls
+
+# 生成性能分析报告
+gprof ./webserver gmon.out > perf.txt
+cat perf.txt
+```
+
+> [!warning] gprof 使用注意
+> 1. 编译时必须加 `-pg` 选项
+> 2. 程序需要正常退出(`Ctrl+C` 终止可能无法生成 `gmon.out`)
+> 3. 分析结果包括各函数的调用次数和执行时间占比
+
+### 5.3 性能监测指标
+
+> [!important] 实验需要监测的关键指标
+>
+> **线程池指标**:
+> - 线程池中线程的 **平均活跃时间** 及 **阻塞时间**
+> - 线程 **最高/最低/平均活跃数量**
+>
+> **消息队列指标**:
+> - filename queue 中的消息长度(积压情况)
+> - msg queue 中的消息长度(积压情况)
+>
+> **系统资源指标**:
+> - 系统 I/O 使用率(`iostat`)
+> - 内存使用情况(`vmstat`)
+> - CPU 使用率和等待率(`vmstat`)
+
+---
+
+## 六、性能对比分析
+
+### 不同模型的性能对比
+
+| 模型 | 优点 | 缺点 | 适用场景 |
+|------|------|------|----------|
+| 迭代式服务器 | 实现简单 | 无法并发处理 | 学习演示 |
+| 多进程服务器([[实践02_多进程多线程服务器]]) | 进程隔离,稳定性好 | fork 开销大 | 连接数少 |
+| 多线程服务器([[实践02_多进程多线程服务器]]) | 共享内存,开销小 | 线程数爆炸风险 | 通用场景 |
+| 预线程化服务器 | 线程复用,无创建开销 | 固定线程数 | 高并发 |
+| 业务分割模型 | 流水线并行,吞吐量高 | 复杂度高,队列延迟 | 极高并发 |
+
+### 业务分割模型的优缺点
+
+> [!success] 优势
+> 1. **流水线并行**: 三个阶段可以同时处理不同的请求
+> 2. **资源隔离**: I/O 密集操作和计算操作分别由不同线程池处理
+> 3. **可独立扩展**: 每个阶段的线程数可以根据瓶颈独立调整
+> 4. **缓存友好**: 文件读取线程可以实现文件内容缓存
+
+> [!failure] 劣势
+> 1. **复杂度高**: 需要管理多个线程池和消息队列
+> 2. **队列延迟**: 消息在队列中排队等待会产生额外延迟
+> 3. **内存开销**: 消息队列和中间数据结构占用额外内存
+> 4. **调试困难**: 多线程池间的数据流转增加了调试难度
+
+---
+
+## 七、常见问题与解决
+
+| 问题 | 原因 | 解决方法 |
+|------|------|----------|
+| 编译报错 `undefined reference to pthread` | 未链接 pthread 库 | 编译时加 `-lpthread` |
+| 队列满导致线程阻塞 | 消息队列容量不足 | 增大 `QUEUE_CAPACITY` 或增加下游线程数 |
+| 文件读取阶段成为瓶颈 | 磁盘 I/O 速度限制 | 增加 read file 线程数,或引入文件缓存 |
+| 内存泄漏 | response 未 `free` | 确保 send 线程发送后释放内存 |
+| 连接泄漏 | 异常路径未 `close(conn_fd)` | 在每个异常处理分支都关闭 socket |
+| 线程安全问题 | 共享变量未加锁 | 使用互斥锁保护所有共享数据 |
+| http_load 测试连接被拒 | 服务器未启动或端口错误 | 确认服务器运行状态和端口号 |
+
+---
+
+## 八、实验总结
+
+通过本实验,应掌握以下能力:
+
+1. **线程池设计与实现**: 理解预线程化技术,掌握基于生产者-消费者模型的线程池实现方法
+2. **业务分割架构**: 理解将复杂业务拆分为多个阶段并行处理的设计思想
+3. **消息队列设计**: 掌握线程间安全通信的消息队列实现
+4. **性能测试方法**: 学会使用 `http_load`、`vmstat`、`iostat`、`gprof` 等工具进行全面的性能测试
+5. **性能优化思路**: 通过对比分析不同模型的性能指标,理解并发服务器的优化方向
+
+> [!question] 思考题
+> 1. 如果文件读取阶段成为瓶颈,除了增加线程数外,还有哪些优化策略?
+> 2. 业务分割模型中,如何确定每个线程池的最佳线程数量?
+> 3. 与 [[10_并发服务器]] 中的预线程化模型相比,业务分割模型在什么场景下优势更明显?
+> 4. 如果需要支持动态内容(如 CGI),业务分割模型应如何调整?
+
+---
+
+## 相关链接
+
+- [[07_多线程编程]] - pthread 编程基础、生产者-消费者模型
+- [[10_并发服务器]] - 预线程化服务器模型
+- [[实践02_多进程多线程服务器]] - 多进程和多线程服务器实现