3. 多进程并发服务器
任务说明
本案例实现一个基于fork()的多进程并发回显服务器。服务器监听TCP端口,每接受一个客户端连接即创建子进程处理该连接上的数据回显,父进程继续等待新连接。客户端发送任意文本,服务器原样返回。
核心实现
// echo_server_fork.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#define PORT 8888
#define BUFFER_SIZE 1024
// 回收子进程,防止僵尸进程
void sigchld_handler(int sig) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main() {
int listen_fd, conn_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
ssize_t n;
// 创建监听socket
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
perror("socket");
exit(1);
}
// 设置端口复用,便于快速重启
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
close(listen_fd);
exit(1);
}
// 监听
if (listen(listen_fd, 10) < 0) {
perror("listen");
close(listen_fd);
exit(1);
}
// 注册SIGCHLD信号处理,回收子进程
signal(SIGCHLD, sigchld_handler);
printf("Echo server listening on port %d (PID=%d)\n", PORT, getpid());
while (1) {
conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
if (conn_fd < 0) {
perror("accept");
continue;
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
close(conn_fd);
continue;
}
if (pid == 0) {
// 子进程:处理该连接
close(listen_fd); // 子进程不需要监听socket
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
printf("[Child %d] serving client %s:%d\n",
getpid(), client_ip, ntohs(client_addr.sin_port));
while ((n = read(conn_fd, buffer, sizeof(buffer))) > 0) {
write(conn_fd, buffer, n); // 原样回显
}
close(conn_fd);
printf("[Child %d] client disconnected\n", getpid());
exit(0);
} else {
// 父进程:关闭已连接的socket,继续accept
close(conn_fd);
}
}
close(listen_fd);
return 0;
}
编译与运行
# 编译
gcc -o echo_server_fork echo_server_fork.c
# 运行服务器
./echo_server_fork
# 另开终端,启动多个客户端测试
telnet 127.0.0.1 8888
# 或使用 nc
nc 127.0.0.1 8888
验证方法
启动服务器后,依次打开两个或更多终端,分别用nc 127.0.0.1 8888连接服务器。在每个客户端中输入任意文本(如hello),观察是否立即收到相同内容的回显。同时查看服务器终端输出,应看到每个客户端连接对应不同的子进程PID,且各客户端之间的通信互不干扰——一个客户端断开不影响其他客户端的回显服务。
关键说明
- 并发模型:每个客户端连接由独立的子进程处理,子进程之间互不影响。父进程只负责
accept()和fork(),不参与数据收发。 - 文件描述符:
fork()后子进程继承父进程的listen_fd和conn_fd。子进程必须关闭listen_fd,父进程必须关闭conn_fd,否则连接不会真正释放。 - 僵尸进程回收:通过
SIGCHLD信号处理器调用waitpid()回收子进程,避免僵尸进程积累。 - 验证判定标准:多个客户端同时连接均能获得独立回显;服务器终端显示不同子进程PID;关闭任一客户端不影响其他客户端连接。