这一篇要解决什么问题?
在第一篇里,我们实现了一个最简单的 TCP 服务端:
accept → read → write → close
问题是:
❌所有请求串行执行
也就是说:
- 一个慢请求 → 卡住后面所有请求
- 服务端几乎没有并发能力
本篇目标
把模型升级为:
主线程: accept 新连接 ↓ 把连接丢给线程池 ↓ worker 线程处理请求👉 核心变化:
accept 和请求处理解耦
一、整体架构图(非常重要)
客户端 │ ▼ ┌────────────┐ │ 主线程 │ ← 只负责 accept └────────────┘ │ ▼ ┌────────────┐ │ 线程池队列 │ └────────────┘ │ ▼ ┌────────────┐ │ worker线程 │ ← read / write / close └────────────┘二、为什么必须这样拆?
❌ 错误模型(第一篇)
accept → read → write → close 👉 一个连接没处理完,accept 就不能继续✅ 正确模型(本篇)
accept → 任务入队 → worker处理 👉 主线程永远在“接活”三、核心思想(一定要记住)
线程池不是工具,而是服务端的执行引擎
分工模型
| 角色 | 职责 |
|---|---|
| 主线程 | accept 连接 |
| 线程池 | 调度任务 |
| worker线程 | 处理请求 |
四、核心改造点
我们只改一件事:
第一篇代码:
int client_fd = accept(...); // 直接处理 read(client_fd, ...); write(client_fd, ...); close(client_fd);改成:
int client_fd = accept(...); // 丢给线程池 pool.submit([client_fd]() { // read / write / close });👉 就这一行,完成架构升级。
五、完整代码(线程池接入版)
👉 假设你已经有 ThreadPool(上一章写的)
#include <iostream> #include <cstring> #include <unistd.h> #include <arpa/inet.h> #include "ThreadPool.h" // 你之前实现的线程池 void handle_client(int client_fd) { char buffer[1024] = {0}; read(client_fd, buffer, sizeof(buffer)); std::cout << "Received:\n" << buffer << std::endl; const char* response = "HTTP/1.1 200 OK\r\n" "Content-Length: 13\r\n" "\r\n" "Hello, World!"; write(client_fd, response, strlen(response)); close(client_fd); } int main() { // 1. 创建线程池 ThreadPool pool(4, 100); // 2. 创建 socket int server_fd = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_port = htons(8080); addr.sin_addr.s_addr = INADDR_ANY; bind(server_fd, (sockaddr*)&addr, sizeof(addr)); listen(server_fd, 10); std::cout << "Server running on port 8080..." << std::endl; while (true) { sockaddr_in client_addr{}; socklen_t len = sizeof(client_addr); int client_fd = accept(server_fd, (sockaddr*)&client_addr, &len); // ⭐ 核心:丢给线程池 pool.submit([client_fd]() { handle_client(client_fd); }); } return 0; }六、流程再走一遍(必须脑子有图)
客户端请求 ↓ 主线程 accept ↓ 得到 client_fd ↓ submit 到线程池 ↓ worker 线程执行 handle_client ↓ read → write → close七、关键细节(面试必问)
1️⃣ 为什么 client_fd 可以传给线程池?
pool.submit([client_fd]() { ... });👉 因为:
- fd 是一个 int(值拷贝)
- 每个连接独立
2️⃣ 为什么必须在 worker 里 close?
👉 因为:
- 谁用谁关闭
- 生命周期归 worker
3️⃣ 主线程为什么不能 read?
👉 因为:
会阻塞 accept
4️⃣ 这个模型算高并发吗?
👉 ❌ 不算
因为:
- 还是阻塞 IO
- 每个连接占一个线程
八、这个版本的瓶颈
问题 1:线程数量有限
1000连接 → 需要1000线程 ❌
问题 2:read 是阻塞的
客户端慢 → worker线程卡住
问题 3:上下文切换开销大
👉 所以:
这个模型只是过渡版本
九、这一篇真正的价值
不是“并发更快了”,而是:你第一次真正建立了这个认知:
accept ≠ 处理请求
👉 服务端的本质是:
连接接入层 + 执行层分离
十、对标 Java(再强化一次)
Java 写法
ExecutorService pool = ... while (true) { Socket client = server.accept(); pool.submit(() -> { handle(client); }); }👉 一模一样。
十一、你现在完成了什么?
你已经:
✔ 从“单线程服务端” → “并发服务端”
✔ 从“同步模型” → “任务调度模型”
✔ 从“写代码” → “搭架构”
十二、下一步(关键升级)
现在的问题是:
❌ 一个连接仍然占一个线程
下一篇
👉《C++ 高性能网络服务骨架(三)》—— 请求分发、连接管理与服务骨架设计
下一篇会解决:
- 请求解析(不是 raw read)
- 响应封装
- 错误处理
- 日志体系
- 连接生命周期管理
总结
这一篇你一定要记住一句话:
线程池 = 服务端执行引擎
主线程只负责:
接连接
worker 线程负责:
干活