跳到主要内容 TCP 服务器高并发实现:单进程、多进程及多线程模型详解 | 极客日志
C++
TCP 服务器高并发实现:单进程、多进程及多线程模型详解 TCP 服务器高并发实现涉及单进程、多进程及多线程三种模型。单进程模型无法处理并发连接;多进程模型通过 fork 创建子进程处理客户端请求,需解决僵尸进程问题(如双 fork 或忽略 SIGCHLD);多线程模型利用线程共享内存优势,资源消耗低于多进程。文章通过 C++ Socket 编程示例对比了三种模型的代码实现、优缺点及适用场景,帮助开发者理解 TCP 网络编程的核心框架与并发设计思想。
蓝绿部署 发布于 2026/3/17 更新于 2026/4/18 3 浏览TCP 服务器整体流程
一个最基础的 TCP 服务器,需要经历以下步骤:
socket() bind() listen() accept() read()/write() close()
流程图可以理解为:
创建套接字 → 绑定端口 → 开始监听 → 等待客户端连接 → 收发数据 → 关闭连接
我们知道 TCP 是连接的、可靠的传输层协议,所以每一个客户端在访问服务器的时候都会建立连接(也就是三次握手)。在客户端没有申请建立连接的时候,服务器要始终保持监听状态(调用系统调用接口 listen),因为用户可能在任意时间对服务器进行访问。一旦客户端进行申请连接(调用系统调用接口 connect),服务器就得与客户端进行连接(调用系统调用接口 accept),然后与客户端进行通信,通信结束后进行关闭。
为什么有 listenfd 还要 accept 生成新的 sockfd?
这是一个关于 TCP 网络编程中的常见问题。我们通过 socket 已经创建了一个 listenfd,为什么还要系统调用 accept 再创建一个 sockfd 呢?
我们可以用生活中的例子来理解:商场的美食区派专人拉客(张三),吸引顾客进店后交给店内员工(李四)接待。张三继续出去拉客,李四负责服务。这样张三一直负责监听新顾客,李四负责具体服务。
这就是 TCP 的设计思想:
listenfd:专门负责接收新连接
sockfd:专门负责和客户端通信
单进程版本
TCP 服务端
class Server {
public :
Server (uint16_t server_port, std::string server_ip) : server_port_ (server_port), server_ip_ (server_ip) { }
void init () {
listenfd_ = socket (AF_INET, SOCK_STREAM, 0 );
if (listenfd_ < 0 ) {
printf ("listenfd_ create error!!!\n" );
exit (1 );
}
printf ("listenfd_ create successful!!!, listenfd_:%d\n" , listenfd_);
struct sockaddr_in server;
memset (&server, 0 , sizeof server);
server.sin_family = AF_INET;
server.sin_port = (server_port_);
(AF_INET, server_ip_. (), &(server.sin_addr));
( (listenfd_, ( sockaddr *)&server, (server)) < ) {
( );
( );
}
( );
( (listenfd_, ) < ) {
( );
( );
}
( );
}
{
( ) {
buffer[ ];
s = (sockfd, buffer, (buffer) - );
(s > ) {
buffer[s] = ;
( , buffer);
std::string info = ;
info += buffer;
(sockfd, info. (), info. ());
} (s == ) {
( );
;
} {
( );
;
}
}
}
{
( ) {
client;
len = (client);
sockfd = (listenfd_, ( sockaddr *)&client, &len);
(sockfd < ) {
( );
}
( , sockfd);
client_port = (client.sin_port);
client_ip[ ];
(AF_INET, &(client.sin_addr), client_ip, client_ip);
(sockfd, client_ip, client_port);
(sockfd);
}
}
~ () {
(listenfd_);
}
:
listenfd_;
server_port_;
std::string server_ip_;
};
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online
htons
inet_pton
c_str
if
bind
struct
sizeof
0
printf
"bind fail !!!\n"
exit
2
printf
"bind successful!!!\n"
if
listen
10
0
printf
"listen fail!!!\n"
exit
3
printf
"listen successful!!!\n"
void service (int sockfd, std::string client_ip, uint16_t client_port)
while
1
char
1024
ssize_t
read
sizeof
1
if
0
0
printf
"client say:%s\n"
"server say:"
write
c_str
size
else
if
0
printf
"client quit !!!\n"
break
else
printf
"read error !!!\n"
break
void start ()
while
1
struct
sockaddr_in
socklen_t
sizeof
int
accept
struct
if
0
printf
"accept fail!!!\n"
printf
"get a new link !!!, sockfd : %d\n"
uint16_t
ntohs
char
16
inet_ntop
sizeof
service
close
Server
close
private
int
uint16_t
TCP 客户端 int main (int argc, char *argv[]) {
if (argc != 3 ) {
printf ("\n\t Usage: %s serverip serverport!\n" );
exit (1 );
}
uint16_t server_port = atoi (argv[2 ]);
std::string server_ip = argv[1 ];
int sockfd = socket (AF_INET, SOCK_STREAM, 0 );
if (sockfd < 0 ) {
printf ("sockfd fail !!!\n" );
exit (2 );
}
printf ("sock successful !!!, sockfd : %d\n" , sockfd);
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);
if (connect (sockfd, (struct sockaddr *)&server, sizeof server) < 0 ) {
printf ("connect fail!!!\n" );
}
while (1 ) {
std::string message;
std::getline (std::cin, message);
write (sockfd, message.c_str (), message.size ());
char buffer[1024 ];
ssize_t s = read (sockfd, buffer, sizeof (buffer) - 1 );
if (s > 0 ) {
buffer[s] = 0 ;
printf ("%s\n" , buffer);
} else if (s == 0 ) {
printf ("server close\n" );
break ;
}
}
close (sockfd);
return 0 ;
}
这样一个简单的单进程版本的基于 TCP 的 socket 网络编程就成功实现了。但是存在如下问题:由于我们的服务器现在是单进程的,当一个客户端连接到服务器之后,会调用 service 进行服务,而 service 是循环执行,只有当客户端退出的时候,service 才能退出。这就导致我们无法继续监听,另一个客户端访问时没有反应,只有当一个客户端退出之后,这个客户端才得以访问。作为一个服务器,这样的问题是不可以存在的,所以需要改为多进程版本。
多进程版本 TCP 服务器
双 fork class Server {
public :
Server (uint16_t server_port, std::string server_ip) : server_port_ (server_port), server_ip_ (server_ip) { }
void init () {
listenfd_ = socket (AF_INET, SOCK_STREAM, 0 );
if (listenfd_ < 0 ) {
printf ("listenfd_ create error!!!\n" );
exit (1 );
}
printf ("listenfd_ create successful!!!, listenfd_:%d\n" , listenfd_);
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));
if (bind (listenfd_, (struct sockaddr *)&server, sizeof (server)) < 0 ) {
printf ("bind fail !!!\n" );
exit (2 );
}
printf ("bind successful!!!\n" );
if (listen (listenfd_, 10 ) < 0 ) {
printf ("listen fail!!!\n" );
exit (3 );
}
printf ("listen successful!!!\n" );
}
void service (int sockfd, std::string client_ip, uint16_t client_port) {
while (1 ) {
char buffer[1024 ];
ssize_t s = read (sockfd, buffer, sizeof (buffer) - 1 );
if (s > 0 ) {
buffer[s] = 0 ;
printf ("client say:%s\n" , buffer);
std::string info = "server say:" ;
info += buffer;
write (sockfd, info.c_str (), info.size ());
} else if (s == 0 ) {
printf ("client quit !!!\n" );
break ;
} else {
printf ("read error !!!\n" );
break ;
}
}
}
void start () {
while (1 ) {
struct sockaddr_in client;
socklen_t len = sizeof (client);
int sockfd = accept (listenfd_, (struct sockaddr *)&client, &len);
if (sockfd < 0 ) {
printf ("accept fail!!!\n" );
}
printf ("get a new link !!!, sockfd : %d\n" , sockfd);
uint16_t client_port = ntohs (client.sin_port);
char client_ip[16 ];
inet_ntop (AF_INET, &(client.sin_addr), client_ip, sizeof client_ip);
pid_t id = fork();
if (id == 0 ) {
close (listenfd_);
if (fork() > 0 ) {
exit (0 );
}
service (sockfd, client_ip, client_port);
close (sockfd);
exit (0 );
}
close (sockfd);
pid_t ret = waitpid (id, nullptr , 0 );
}
}
~Server () {
close (listenfd_);
}
private :
int listenfd_;
uint16_t server_port_;
std::string server_ip_;
};
通过这样的方式就可以很好的保证多个客户端都可以享受到服务器的服务。
解释说明:首先我们创建子进程之后,父进程必须关掉 sockfd。这是因为随着客户端的访问人数增多,如果我们不关闭,就会导致文件描述符一直增多,并且我们将任务交给子进程处理之后,父进程就不需要再继续占用这个文件描述符了。而在子进程中,继续创建子进程(孙子进程),然后让子进程直接退出,这样父进程就可以直接获取到退出结果,也就不需要继续等待。对于孙子进程,当它的父进程(也就是子进程)退出之后,孙子进程就会被托孤,交给操作系统进行回收。
1️⃣ 父进程必须关闭 sockfd
2️⃣ 双 fork 防止僵尸进程 父进程 └── 子进程 └── 孙子进程
当子进程退出:
孙子进程变成孤儿进程
由 init 接管
自动回收
忽略 SIGCHLD 信号 我们还可以不用父进程进行等待,利用信号机制,通过对 SIGCHLD 信号进行忽略,这样子进程在退出之后,也不会出现僵尸进程,同时父进程还可以继续监听。
class Server {
public :
Server (uint16_t server_port, std::string server_ip) : server_port_ (server_port), server_ip_ (server_ip) { }
void init () {
listenfd_ = socket (AF_INET, SOCK_STREAM, 0 );
if (listenfd_ < 0 ) {
printf ("listenfd_ create error!!!\n" );
exit (1 );
}
printf ("listenfd_ create successful!!!, listenfd_:%d\n" , listenfd_);
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));
if (bind (listenfd_, (struct sockaddr *)&server, sizeof (server)) < 0 ) {
printf ("bind fail !!!\n" );
exit (2 );
}
printf ("bind successful!!!\n" );
if (listen (listenfd_, 10 ) < 0 ) {
printf ("listen fail!!!\n" );
exit (3 );
}
printf ("listen successful!!!\n" );
}
void service (int sockfd, std::string client_ip, uint16_t client_port) {
while (1 ) {
char buffer[1024 ];
ssize_t s = read (sockfd, buffer, sizeof (buffer) - 1 );
if (s > 0 ) {
buffer[s] = 0 ;
printf ("client say:%s\n" , buffer);
std::string info = "server say:" ;
info += buffer;
write (sockfd, info.c_str (), info.size ());
} else if (s == 0 ) {
printf ("client quit !!!\n" );
break ;
} else {
printf ("read error !!!\n" );
break ;
}
}
}
void start () {
signal (SIGCHLD, SIG_IGN);
while (1 ) {
struct sockaddr_in client;
socklen_t len = sizeof (client);
int sockfd = accept (listenfd_, (struct sockaddr *)&client, &len);
if (sockfd < 0 ) {
printf ("accept fail!!!\n" );
}
printf ("get a new link !!!, sockfd : %d\n" , sockfd);
uint16_t client_port = ntohs (client.sin_port);
char client_ip[16 ];
inet_ntop (AF_INET, &(client.sin_addr), client_ip, sizeof client_ip);
pid_t id = fork();
if (id == 0 ) {
close (listenfd_);
service (sockfd, client_ip, client_port);
close (sockfd);
exit (0 );
}
close (sockfd);
}
}
~Server () {
close (listenfd_);
}
private :
int listenfd_;
uint16_t server_port_;
std::string server_ip_;
};
signal(SIGCHLD, SIG_IGN);
多线程版本 TCP 服务器 当客户越来越多时,我们可以通过多线程的方式。注意类成员函数默认隐藏一个 this 指针,而多线程的处理函数中要求函数签名为 void* (*)(void*),因此会造成类型不匹配的问题。为了解决这个问题我们要让它成为一个全局的函数,所以要增加 static。而且全局函数想要调用类内方法是不可以的,所以我们增加了一个 ThreadData 结构体传递 this 指针。
struct ThreadData {
ThreadData (int sockfd, int16_t client_port, std::string client_ip, Server *srv)
: sockfd_ (sockfd), client_port_ (client_port), client_ip_ (client_ip), srv_ (srv) { }
int sockfd_;
int16_t client_port_;
std::string client_ip_;
Server *srv_;
};
class Server {
public :
Server (uint16_t server_port, std::string server_ip) : server_port_ (server_port), server_ip_ (server_ip) { }
void init () {
listenfd_ = socket (AF_INET, SOCK_STREAM, 0 );
if (listenfd_ < 0 ) {
printf ("listenfd_ create error!!!\n" );
exit (1 );
}
printf ("listenfd_ create successful!!!, listenfd_:%d\n" , listenfd_);
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));
if (bind (listenfd_, (struct sockaddr *)&server, sizeof (server)) < 0 ) {
printf ("bind fail !!!\n" );
exit (2 );
}
printf ("bind successful!!!\n" );
if (listen (listenfd_, 10 ) < 0 ) {
printf ("listen fail!!!\n" );
exit (3 );
}
printf ("listen successful!!!\n" );
}
void service (int sockfd, std::string client_ip, uint16_t client_port) {
while (1 ) {
char buffer[1024 ];
ssize_t s = read (sockfd, buffer, sizeof (buffer) - 1 );
if (s > 0 ) {
buffer[s] = 0 ;
printf ("client say:%s\n" , buffer);
std::string info = "server say:" ;
info += buffer;
write (sockfd, info.c_str (), info.size ());
} else if (s == 0 ) {
printf ("client quit !!!\n" );
break ;
} else {
printf ("read error !!!\n" );
break ;
}
}
}
static void *routine (void *arg) {
pthread_detach (pthread_self ());
ThreadData *td = (ThreadData *)arg;
td->srv_->service (td->sockfd_, td->client_ip_, td->client_port_);
close (td->sockfd_);
delete td;
return nullptr ;
}
void start () {
while (1 ) {
struct sockaddr_in client;
socklen_t len = sizeof (client);
int sockfd = accept (listenfd_, (struct sockaddr *)&client, &len);
if (sockfd < 0 ) {
printf ("accept fail!!!\n" );
}
printf ("get a new link !!!, sockfd : %d\n" , sockfd);
uint16_t client_port = ntohs (client.sin_port);
char client_ip[16 ];
inet_ntop (AF_INET, &(client.sin_addr), client_ip, sizeof client_ip);
pthread_t tid;
ThreadData *td = new ThreadData (sockfd, client_port, client_ip, this );
pthread_create (&tid, nullptr , routine, td);
}
}
~Server () {
close (listenfd_);
}
private :
int listenfd_;
uint16_t server_port_;
std::string server_ip_;
};
三种模型对比总结 模型 并发能力 资源消耗 适用场景 单进程 ❌ 低 理解 多进程 ✅ 高 中小并发 多线程 ✅ 中 常见服务器
我们从最简单的单进程版本,一步一步改造成多进程、多线程版本。代码变多了,结构也复杂了,但核心其实很简单:让服务器同时服务多个客户端。单进程能跑起来,多进程能并发,多线程更高效。现在相信我们对 TCP 服务器的整体框架就有了一定清晰的认识了。