TCP(传输控制协议)作为面向连接、可靠的传输层协议,是大多数网络应用的基石。本文基于实战视角,从最基础的 TCP 回显服务器入手,逐步解决 “单连接阻塞” 问题,通过多进程、多线程、线程池三种方案优化并发能力,最终实现支持远程命令执行的 TCP 服务,帮你彻底掌握 TCP Socket 编程的核心逻辑与进阶技巧。
一、TCP 编程基础:核心 API 与连接流程
TCP 与 UDP 的 “无连接” 特性不同,其通信需经过 “三次握手建立连接→数据传输→四次挥手关闭连接” 的完整流程。首先需掌握支撑这一流程的关键 Socket API。
1.1 核心 API 详解
TCP 编程依赖的核心函数均定义在<sys/socket.h>中,各函数的功能与参数如下:
| API | 功能 | 关键参数说明 | 返回值 |
|---|---|---|---|
socket() | 创建网络通信端点(文件描述符) | domain=AF_INET(IPv4)、type=SOCK_STREAM(TCP 流)、protocol=0(默认协议) | 成功返回文件描述符(≥0),失败返回 - 1 |
bind() | 将 socket 绑定到固定 IP 和端口 | sockfd(socket 描述符)、addr(本地地址结构)、addrlen(地址结构长度) | 成功返回 0,失败返回 - 1 |
listen() | 将 socket 设为监听状态,等待客户端连接 | sockfd(socket 描述符)、backlog(最大等待连接数,通常设 5-10) | 成功返回 0,失败返回 - 1 |
accept() | 接受客户端连接请求(阻塞) | sockfd(监听 socket)、addr(传出客户端地址)、addrlen(地址长度,传入传出) | 成功返回新的通信 socket,失败返回 - 1 |
connect() | 客户端发起连接请求 | sockfd(客户端 socket)、addr(服务器地址结构)、addrlen(地址长度) | 成功返回 0,失败返回 - 1 |
read()/write() | 读写 TCP 数据(基于文件描述符) | sockfd(通信 socket)、buf(数据缓冲区)、count(数据长度) | 成功返回读写字节数,失败返回 - 1,read返回 0 表示对端关闭 |
1.2 TCP 通信流程(服务器 - 客户端)
TCP 的通信流程是 “固定套路”,理解这一流程是编写代码的前提:
-
服务器端:
- 调用
socket()创建监听 socket(_listensock); - 调用
bind()将监听 socket 绑定到固定端口; - 调用
listen()将监听 socket 设为 “监听状态”; - 循环调用
accept(),阻塞等待客户端连接,成功后返回 “通信 socket”(connfd); - 通过
read()/write()与客户端通信; - 通信结束后,关闭
connfd。
- 调用
-
客户端:
- 调用
socket()创建客户端 socket; - 调用
connect()发起连接(无需手动bind,操作系统自动分配随机端口); - 通过
read()/write()与服务器通信; - 通信结束后,关闭客户端 socket。
- 调用
1.3 关键注意点
地址结构初始化:绑定地址时需将struct sockaddr_in清零,并设置sin_family=AF_INET、sin_port=htons(端口)(主机字节序转网络字节序)、sin_addr.s_addr=htonl(INADDR_ANY)(绑定所有网卡 IP)。
SO_REUSEADDR选项:通过setsockopt()设置该选项,避免 “端口占用” 错误(服务重启时,旧连接的 TIME_WAIT 状态会占用端口)。
accept()的返回值:accept()返回的是 “新的通信 socket”,监听 socket(_listensock)仅用于接受连接,不参与数据传输。
二、V1 版本:基础 TCP 回显服务器(单连接)
回显服务器的核心功能是 “接收客户端消息,原样返回”,通过这个案例可掌握 TCP 编程的基础框架。
2.1 核心代码实现
首先定义nocopy基类避免对象拷贝(与 UDP 案例一致,防止 socket 重复关闭),再实现 TCP 服
// nocopy.hpp(禁止拷贝基类)
#pragma once
class nocopy {
public:
nocopy() = default;
~nocopy() = default;
// 禁止拷贝构造和赋值运算符
nocopy(const nocopy&) = delete;
const nocopy& operator=(const nocopy&) = delete;
};
// Comm.hpp(错误码与地址转换宏)
#pragma once
#include <sys/socket.h>
#include <netinet/in.h>
// 错误码定义
enum {
Usage_Err = 1,
Socket_Err,
Bind_Err,
Listen_Err
};
// 地址结构转换宏(struct sockaddr_in* → struct sockaddr*)
#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)
// TcpServer.hpp(V1版本:单连接回显服务器)
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "nocopy.hpp"
#include "Comm.hpp"
const static int default_backlog = 6; // 最大等待连接数
class TcpServer : public nocopy {
public:
TcpServer(uint16_t port) : _port(port), _isrunning(false), _listensock(-1) {}
// 初始化:创建socket → 绑定 → 监听
void Init() {
// 1. 创建监听socket
_listensock = socket(AF_INET, SOCK_STREAM, 0);
if (_listensock < 0) {
std::cerr << "socket error: " << strerror(errno) << std::endl;
exit(Socket_Err);
}
// 设置SO_REUSEADDR,避免端口占用
int opt = 1;
setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
std::cout << "create socket success, sockfd: " << _listensock << std::endl;
// 2. 绑定地址(IP + 端口)
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET; // IPv4
local.sin_port = htons(_port); // 端口转网络字节序
local.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定所有网卡IP
if (bind(_listensock, CONV(&local), sizeof(local)) != 0) {
std::cerr << "bind error: " << strerror(errno) << std::endl;
close(_listensock);
exit(Bind_Err);
}
std::cout << "bind socket success, sockfd: " << _listensock << std::endl;
// 3. 设为监听状态
if (listen(_listensock, default_backlog) != 0) {
std::cerr << "listen error: " << strerror(errno) << std::endl;
close(_listensock);
exit(Listen_Err);
}
std::cout << "listen socket success, sockfd: " << _listensock << std::endl;
}
// 服务逻辑:接收客户端消息并回显
void Service(int connfd) {
char buffer[1024];
while (true) {
// 读取客户端消息(阻塞)
ssize_t n = read(connfd, buffer, sizeof(buffer) - 1);
if (n > 0) {
buffer[n] = '\0'; // 确保字符串结束
std::cout << "client say# " << buffer << std::endl;
// 回显消息给客户端
std::string echo_msg = "server echo# " + std::string(buffer);
write(connfd, echo_msg.c_str(), echo_msg.size());
} else if (n == 0) {
// read返回0 → 客户端关闭连接
std::cout << "client quit..." << std::endl;
break;
} else {
// 读取出错
std::cerr << "read error: " << strerror(errno) << std::endl;
break;
}
}
close(connfd); // 关闭通信socket
}
// 启动服务:循环接受连接
void Start() {
_isrunning = true;
while (_isrunning) {
struct sockaddr_in peer; // 客户端地址
socklen_t peer_len = sizeof(peer);
// 接受客户端连接(阻塞,成功返回通信socket)
int connfd = accept(_listensock, CONV(&peer), &peer_len);
if (connfd < 0) {
std::cerr << "accept error: " << strerror(errno) << std::endl;
continue;
}
std::cout << "accept success, new connfd: " << connfd << std::endl;
// 处理当前连接(单连接阻塞,无法处理其他连接)
Service(connfd);
}
close(_listensock); // 关闭监听socket
}
private:
uint16_t _port; // 服务端口
int _listensock; // 监听socket描述符
bool _isrunning; // 服务运行状态
};
2.2 客户端实现
客户端逻辑简单:创建 socket → 发起连接 → 循环发送 / 接收消息:
// TcpClient.cpp
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Comm.hpp"
// 打印使用帮助
void Usage(const std::string& process) {
std::cout << "Usage: " << process << " server_ip server_port" << std::endl;
}
int main(int argc, char* argv[]) {
if (argc != 3) {
Usage(argv[0]);
return Usage_Err;
}
// 解析服务器地址
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
// 1. 创建客户端socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
std::cerr << "socket error: " << strerror(errno) << std::endl;
return Socket_Err;
}
// 2. 发起连接(无需手动bind,OS自动分配端口)
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr); // 字符串IP转网络字节序
if (connect(sockfd, CONV(&server), sizeof(server)) < 0) {
std::cerr << "connect error: " << strerror(errno) << std::endl;
close(sockfd);
return 1;
}
// 3. 循环发送/接收消息
std::string msg;
char buffer[1024];
while (true) {
std::cout << "Please Enter# ";
std::getline(std::cin, msg);
// 发送消息给服务器
write(sockfd, msg.c_str(), msg.size());
// 接收服务器回显
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0) {
buffer[n] = '\0';
std::cout << "server echo: " << buffer << std::endl;
} else if (n == 0 || n < 0) {
// 服务器关闭或读取出错
std::cout << "server disconnected..." << std::endl;
break;
}
}
close(sockfd);
return 0;
}
2.3 测试与问题
-
测试步骤:
- 启动服务器:
./tcp_server 8888; - 启动客户端:
./tcp_client 127.0.0.1 8888; - 客户端输入消息,服务器会原样回显。
- 启动服务器:
-
核心问题:V1 版本是 “单连接阻塞” 模型 ——
Service(connfd)会阻塞处理当前客户端,直到客户端关闭,期间无法接受新的连接。若同时启动多个客户端,只有第一个客户端能正常通信,其他客户端会卡在 “连接等待” 状态。
三、V2 版本:多进程 TCP 服务器(解决并发)
为解决 “单连接阻塞” 问题,V2 版本通过 “ fork 子进程” 的方式,让每个客户端连接由独立的子进程处理,主进程仅负责接受连接。
3.1 核心思路
主进程:循环调用accept()接受连接,每接受一个连接就fork()一个子进程;
子进程:继承主进程的connfd,负责处理该客户端的通信逻辑,处理完后关闭connfd并退出;
孙子进程优化:子进程fork()后立即退出,让孙子进程成为 “孤儿进程”(由系统进程领养),避免主进程等待子进程回收(减少僵尸进程)。
3.2 关键代码修改
在 V1 基础上,新增ProcessConnection()函数处理连接的 “进程创建” 逻辑,同时引入InetAddr类方便打印客户端地址:
// InetAddr.hpp(客户端地址封装)
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class InetAddr {
public:
InetAddr(struct sockaddr_in& addr) : _addr(addr) {
_ip = inet_ntoa(addr.sin_addr); // 网络字节序IP转字符串
_port = ntohs(addr.sin_port); // 网络字节序端口转主机字节序
}
// 获取IP和端口
std::string Ip() const { return _ip; }
uint16_t Port() const { return _port; }
// 打印客户端地址(如"127.0.0.1:54321")
std::string PrintDebug() const {
return _ip + ":" + std::to_string(_port);
}
private:
std::string _ip; // 客户端IP字符串
uint16_t _port; // 客户端端口(主机字节序)
struct sockaddr_in _addr; // 原生地址结构
};
// TcpServer.hpp(V2版本:多进程修改)
#include "InetAddr.hpp"
#include <sys/wait.h> // 用于waitpid()
// 新增:处理连接——创建子进程处理客户端
void ProcessConnection(int connfd, struct sockaddr_in& peer) {
pid_t id = fork(); // 主进程fork子进程
if (id < 0) {
// fork失败,关闭连接
std::cerr << "fork error: " << strerror(errno) << std::endl;
close(connfd);
return;
} else if (id == 0) {
// 子进程:关闭监听socket(子进程无需监听)
close(_listensock);
// 子进程再fork,让孙子进程处理业务(避免僵尸进程)
if (fork() > 0) {
exit(0); // 子进程退出,孙子进程成为孤儿进程
}
// 孙子进程:处理客户端通信
InetAddr client_addr(peer);
std::cout << "new client connected: " << client_addr.PrintDebug() << std::endl;
Service(connfd); // 复用V1的Service逻辑
close(connfd);
exit(0); // 处理完后退出孙子进程
} else {
// 主进程:关闭通信socket(主进程仅负责accept)
close(connfd);
// 非阻塞回收子进程(避免僵尸进程)
waitpid(id, nullptr, WNOHANG);
}
}
// 修改Start()函数:调用ProcessConnection处理连接
void Start() {
_isrunning = true;
while (_isrunning) {
struct sockaddr_in peer;
socklen_t peer_len = sizeof(peer);
int connfd = accept(_listensock, CONV(&peer), &peer_len);
if (connfd < 0) {
std::cerr << "accept error: " << strerror(errno) << std::endl;
continue;
}
// 交给子进程处理,主进程继续accept
ProcessConnection(connfd, peer);
}
close(_listensock);
}
3.3 测试与问题
-
测试效果:启动多个客户端,每个客户端都能独立与服务器通信,主进程可实时接受新连接。
-
核心问题:进程创建的 “开销大”—— 每个连接对应一个进程,进程的内存占用、上下文切换成本远高于线程,在高并发场景(如千级连接)下会导致系统资源耗尽。
四、V3 版本:多线程 TCP 服务器(轻量级并发)
为降低并发开销,V3 版本用 “线程” 替代 “进程”—— 每个客户端连接由独立的线程处理,线程的内存共享特性和轻量级上下文切换能支持更高并发。
4.1 核心思路
主进程:循环accept()接受连接,每接受一个连接就创建一个线程;
线程:通过 “线程数据结构”(ThreadData)传递connfd和客户端地址,处理完通信后关闭connfd并释放资源;
线程分离:通过pthread_detach()设置线程分离,避免主进程调用pthread_join()回收线程(减少阻塞)。
4.2 关键代码修改
在 V2 基础上,修改ProcessConnection()为线程创建逻辑,新增ThreadData结构体传递线程参数:
// TcpServer.hpp(V3版本:多线程修改)
#include <pthread.h> // 线程相关头文件
// 线程数据结构:传递connfd和客户端地址
class ThreadData {
public:
ThreadData(int sockfd, struct sockaddr_in addr)
: _sockfd(sockfd), _addr(addr) {}
~ThreadData() = default;
int _sockfd; // 通信socket描述符
InetAddr _addr; // 客户端地址
};
// 新增:线程执行函数(必须是void*返回值,void*参数)
static void* ThreadExecute(void* args) {
// 设置线程分离,无需主进程回收
pthread_detach(pthread_self());
// 解析线程参数
ThreadData* td = static_cast<ThreadData*>(args);
if (td == nullptr) return nullptr;
// 处理客户端通信(复用Service逻辑,需修改Service参数)
Service(td->_sockfd, td->_addr);
// 释放资源
close(td->_sockfd);
delete td;
return nullptr;
}
// 修改Service函数:接收客户端地址,打印日志
void Service(int connfd, InetAddr client_addr) {
char buffer[1024];
while (true) {
ssize_t n = read(connfd, buffer, sizeof(buffer) - 1);
if (n > 0) {
buffer[n] = '\0';
std::cout << "client[" << client_addr.PrintDebug() << "] say# " << buffer << std::endl;
std::string echo_msg = "server echo# " + std::string(buffer);
write(connfd, echo_msg.c_str(), echo_msg.size());
} else if (n == 0) {
std::cout << "client[" << client_addr.PrintDebug() << "] quit..." << std::endl;
break;
} else {
std::cerr << "read error from client[" << client_addr.PrintDebug() << "]: " << strerror(errno) << std::endl;
break;
}
}
}
// 修改ProcessConnection:创建线程处理连接
void ProcessConnection(int connfd, struct sockaddr_in& peer) {
// 创建线程数据
ThreadData* td = new ThreadData(connfd, peer);
if (td == nullptr) {
std::cerr << "new ThreadData error" << std::endl;
close(connfd);
return;
}
// 创建线程
pthread_t tid;
if (pthread_create(&tid, nullptr, ThreadExecute, td) != 0) {
std::cerr << "pthread_create error: " << strerror(errno) << std::endl;
delete td;
close(connfd);
return;
}
std::cout << "create thread for client[" << td->_addr.PrintDebug() << "], tid: " << tid << std::endl;
}
4.3 扩展:远程命令执行服务
基于多线程模型,可快速扩展业务逻辑 —— 将 “回显” 改为 “执行客户端发送的命令并返回结果”。新增Command类封装命令执行逻辑:
// Command.hpp(命令执行封装)
#pragma once
#include <iostream>
#include <string>
#include <set>
#include <unistd.h>
#include <stdio.h> // 用于popen()/pclose()
class Command {
public:
Command(int sockfd) : _sockfd(sockfd) {
// 安全措施:仅允许执行指定的安全命令
_safe_commands = {"ls", "pwd", "ls -l", "ll", "who", "whoami", "touch"};
}
// 检查命令是否安全(避免恶意命令如"rm -rf /")
bool IsSafe(const std::string& cmd) const {
return _safe_commands.count(cmd) > 0;
}
// 接收客户端发送的命令
std::string RecvCommand() const {
char buf[1024];
ssize_t n = read(_sockfd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
return buf;
}
return "";
}
// 执行命令并返回结果
std::string Execute(const std::string& cmd) const {
if (!IsSafe(cmd)) return "error: unsafe command\n";
// 使用popen执行命令,读取输出
FILE* fp = popen(cmd.c_str(), "r");
if (fp == nullptr) return "error: execute command failed\n";
char buf[1024];
std::string result;
while (fgets(buf, sizeof(buf), fp) != nullptr) {
result += buf;
}
pclose(fp);
// 无结果时返回提示
return result.empty() ? "execute success (no output)\n" : result;
}
// 发送命令执行结果给客户端
void SendResult(const std::string& result) const {
write(_sockfd, result.c_str(), result.size());
}
private:
int _sockfd; // 通信socket
std::set<std::string> _safe_commands; // 安全命令集合
};
// 修改Service函数:替换为命令执行逻辑
void Service(int connfd, InetAddr client_addr) {
Command cmd_handler(connfd);
while (true) {
// 接收命令
std::string cmd = cmd_handler.RecvCommand();
if (cmd.empty()) {
std::cout << "client[" << client_addr.PrintDebug() << "] quit..." << std::endl;
break;
}
std::cout << "client[" << client_addr.PrintDebug() << "] execute command: " << cmd << std::endl;
// 执行命令并返回结果
std::string result = cmd_handler.Execute(cmd);
cmd_handler.SendResult(result);
}
}
4.4 测试与问题
-
测试效果:客户端发送 “ls”,服务器返回当前目录文件列表;发送 “pwd”,返回当前工作目录。
-
核心问题:线程创建的 “动态开销”—— 高并发场景(如万级连接)下,频繁创建 / 销毁线程会产生大量开销,且线程数量过多会导致 CPU 上下文切换频繁,系统性能下降。
五、V4 版本:线程池 TCP 服务器(高可用并发)
为解决 “动态线程开销” 问题,V4 版本引入 “线程池”—— 提前创建固定数量的线程,通过任务队列接收客户端连接任务,线程循环从队列中取任务处理,避免频繁创建线程。
5.1 核心思路
线程池:初始化时创建 N 个线程(如 4、8 个,根据 CPU 核心数调整),线程阻塞等待任务队列中的任务;
任务队列:主进程accept()接受连接后,将 “处理连接” 封装为任务(std::function),加入任务队列;
线程同步:用互斥锁保护任务队列的读写,用条件变量唤醒等待的线程(避免线程空轮询)。
5.2 线程池实现(ThreadPool.hpp)
首先实现通用线程池类,支持任意类型的任务(基于std::function):
// ThreadPool.hpp(通用线程池)
#pragma once
#include <iostream>
#include <vector>
#include <queue>
#include <functional>
#include <pthread.h>
#include "nocopy.hpp"
template <typename Task>
class ThreadPool : public nocopy {
public:
// 单例模式:确保全局只有一个线程池
static ThreadPool* GetInstance() {
static ThreadPool instance;
return &instance;
}
// 初始化线程池:指定线程数量
void Init(size_t thread_num = 4) {
_thread_num = thread_num;
_is_running = true;
// 创建线程
for (size_t i = 0; i < _thread_num; ++i) {
pthread_t tid;
if (pthread_create(&tid, nullptr, ThreadFunc, this) != 0) {
std::cerr << "pthread_create error" << std::endl;
_is_running = false;
break;
}
_threads.push_back(tid);
}
std::cout << "thread pool init success, thread num: " << _thread_num << std::endl;
}
// 向任务队列添加任务
void Push(const Task& task) {
pthread_mutex_lock(&_mutex);
_task_queue.push(task);
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&_cond); // 唤醒一个等待的线程
}
// 停止线程池
void Stop() {
_is_running = false;
pthread_cond_broadcast(&_cond); // 唤醒所有线程
// 等待所有线程退出
for (pthread_t tid : _threads) {
pthread_join(tid, nullptr);
}
// 释放资源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
private:
ThreadPool() {
// 初始化互斥锁和条件变量
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
}
~ThreadPool() { Stop(); }
// 线程执行函数:循环取任务处理
static void* ThreadFunc(void* args) {
ThreadPool* pool = static_cast<ThreadPool*>(args);
while (pool->_is_running) {
pthread_mutex_lock(&pool->_mutex);
// 任务队列为空时,阻塞等待
while (pool->_task_queue.empty() && pool->_is_running) {
pthread_cond_wait(&pool->_cond, &pool->_mutex);
}
// 线程池停止时退出
if (!pool->_is_running) {
pthread_mutex_unlock(&pool->_mutex);
break;
}
// 取出任务并执行
Task task = pool->_task_queue.front();
pool->_task_queue.pop();
pthread_mutex_unlock(&pool->_mutex);
task(); // 执行任务
}
return nullptr;
}
private:
size_t _thread_num; // 线程数量
bool _is_running; // 线程池运行状态
std::vector<pthread_t> _threads; // 线程ID列表
std::queue<Task> _task_queue; // 任务队列
pthread_mutex_t _mutex; // 保护任务队列的互斥锁
pthread_cond_t _cond; // 唤醒线程的条件变量
};
5.3 TCP 服务器集成线程池
修改TcpServer的ProcessConnection(),将 “处理连接” 封装为任务,加入线程池:
// TcpServer.hpp(V4版本:线程池修改)
#include "ThreadPool.hpp"
#include <functional> // 用于std::function
// 修改ProcessConnection:向线程池提交任务
void ProcessConnection(int connfd, struct sockaddr_in& peer) {
InetAddr client_addr(peer);
// 封装任务:绑定this指针、connfd和客户端地址
using Task = std::function<void()>;
Task task = std::bind(&TcpServer::Service, this, connfd, client_addr);
// 向线程池提交任务
ThreadPool<Task>::GetInstance()->Push(task);
std::cout << "push task for client[" << client_addr.PrintDebug() << "], connfd: " << connfd << std::endl;
}
// 修改Init函数:初始化线程池
void Init() {
// (复用V1的socket创建、绑定、监听逻辑)
// 初始化线程池(4个线程)
ThreadPool<std::function<void()>>::GetInstance()->Init(4);
}
// 修改Start函数:无需额外处理,主进程仅accept
void Start() {
_isrunning = true;
while (_isrunning) {
struct sockaddr_in peer;
socklen_t peer_len = sizeof(peer);
int connfd = accept(_listensock, CONV(&peer), &peer_len);
if (connfd < 0) {
std::cerr << "accept error: " << strerror(errno) << std::endl;
continue;
}
// 提交任务到线程池
ProcessConnection(connfd, peer);
}
// 停止线程池
ThreadPool<std::function<void()>>::GetInstance()->Stop();
close(_listensock);
}
5.4 测试效果
启动服务器后,线程池会提前创建 4 个线程;
同时启动多个客户端,主进程将每个连接封装为任务加入队列;
线程池中的线程循环取任务处理,无需频繁创建线程,系统资源占用稳定。
六、总结与进阶方向
本文通过四个版本的迭代,完整覆盖了 TCP Socket 编程的核心演进路径:
- V1(单连接):掌握 TCP 基础流程,但无法处理并发;
- V2(多进程):解决并发,但进程开销大;
- V3(多线程):降低开销,支持更高并发,但动态线程仍有开销;
- V4(线程池):消除动态线程开销,支持高可用并发,是生产环境的主流方案。
进阶方向
- IO 多路复用:结合
select/poll/epoll(Linux),实现 “单线程处理多连接”,进一步提升并发能力(适合高并发、低数据量场景,如聊天服务器); - 协议定制:当前版本基于 “裸 TCP” 传输,可自定义应用层协议(如添加消息长度字段),解决 “粘包” 问题;
- 安全加固:引入 TLS/SSL(如 OpenSSL 库),实现 HTTPS-like 的加密通信;
- 服务化改造:将服务器封装为可配置、可监控的服务,支持日志轮转、信号处理(如
SIGINT优雅退出)。
1082

被折叠的 条评论
为什么被折叠?



