当前位置: 首页 > news >正文

Linux 34TCP服务器多进程并发

TCP 多进程并发服务器代码讲解:原理、流程与细节

实现了一个 基于 fork() 多进程的 TCP 并发服务器—— 核心能力是:服务器启动后可同时处理多个客户端连接,每个客户端连接由独立的子进程负责数据交互,主进程仅专注于 “接收新连接”,不会被单个客户端的通信阻塞。

核心设计思路

多进程并发的核心逻辑:

  1. 主进程(父进程):只做 3 件事 —— 创建监听 socket、绑定端口、监听连接,以及 accept() 接收新客户端连接;每接收一个新连接,就 fork() 一个子进程专门处理该客户端的后续通信;

  2. 子进程:由主进程 fork() 生成,仅与对应的客户端交互(接收数据、发送响应),不参与监听 / 接收新连接;子进程与客户端断开连接后自动退出,释放资源。

优势:实现简单、进程间资源隔离(一个客户端的异常不会影响其他客户端);缺点:进程创建 / 销毁开销略大,适合连接数适中的场景。

代码分段讲解(按执行流程)

1. 头文件与工具函数 sock_init():初始化服务器监听 socket

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>   // socket 核心API头文件
#include <netinet/in.h>   // 网络地址结构(sockaddr_in)
#include <arpa/inet.h>    // 字节序转换(htons/inet_addr等)
#include <pthread.h>      // 此处未用线程,可能是冗余包含// 功能:创建并初始化服务器监听socket(绑定端口、开始监听)
int sock_init()
{// 1. 创建TCP socket(监听用)// 参数1:AF_INET = IPv4地址族;参数2:SOCK_STREAM = TCP类型;参数3:0 = 默认协议(TCP)int sersockfd = socket(AF_INET, SOCK_STREAM, 0); if (sersockfd == -1)  // socket创建失败返回-1{perror("socket create failed"); // 建议加perror打印错误原因exit(1); // 直接退出程序(实际开发可优化为返回错误码)}// 2. 初始化服务器地址结构(sockaddr_in)struct sockaddr_in saddr; // 存储服务器IP、端口等信息memset(&saddr, 0, sizeof(saddr)); // 清空结构体(避免垃圾值)saddr.sin_family = AF_INET;       // IPv4地址族saddr.sin_port = htons(6000);     // 端口号:htons()将主机字节序转为网络字节序(大端)saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 绑定本地回环IP(仅本机可连接)// 3. 绑定socket与地址结构(将端口6000绑定到sersockfd)int n = bind(sersockfd, (struct sockaddr *)&saddr, sizeof(saddr));if (n == -1)  // 绑定失败(如端口被占用){perror("bind failed");close(sersockfd); // 先关闭已创建的socket,避免资源泄漏exit(1);}// 4. 将socket设为监听状态(等待客户端连接)// 参数2:backlog = 5 → 监听队列最大长度(最多同时有5个未处理的连接请求)n = listen(sersockfd, 5);if (n == -1)  // 监听失败{perror("listen failed");close(sersockfd);exit(1);}return sersockfd; // 返回监听socket的文件描述符(主进程用它accept新连接)
}

2. 主进程逻辑:接收新连接 + 创建子进程

int main()
{// 1. 初始化服务器监听socketint sockfd = sock_init(); // sockfd = 监听socket的文件描述符if (sockfd == -1){exit(1);}printf("server start success: listen 127.0.0.1:6000\n");struct sockaddr_in caddr; // 存储客户端的IP、端口信息(accept时填充)while (1)  // 主进程无限循环:持续接收新连接{int len = sizeof(caddr);// 2. 接收客户端连接(阻塞调用:直到有新客户端连接才返回)// 参数1:监听socket;参数2:客户端地址结构(输出);参数3:地址长度(输入输出)int cilsockfd = accept(sockfd, (struct sockaddr *)&caddr, (unsigned int *)&len);if (cilsockfd < 0)  // accept失败(如被信号中断){perror("accept failed");close(cilsockfd); // 此处cilsockfd是-1,close无效,可省略continue; // 继续等待下一个连接,不退出}// 打印新连接信息:客户端socket、端口(ntohs转为主机字节序)、IP(inet_ntoa转字符串)printf("accept success: cilsockfd=%d, client port=%d, client ip=%s\n", cilsockfd, ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));// 3. 创建子进程:处理当前客户端的通信pid_t pid = fork(); // fork()调用后,父进程和子进程同时执行后续代码if (pid == -1)  // fork失败(如系统资源不足){perror("fork failed");close(cilsockfd); // 关闭客户端socket,避免资源泄漏continue;}// -------------------------- 子进程逻辑(pid == 0)--------------------------if (pid == 0){// 子进程不需要监听socket,立即关闭(避免端口被占用,且父子进程共享文件描述符)close(sockfd); // 循环与客户端通信(接收数据 + 发送响应)while (1){char buff[128] = {0}; // 存储接收的客户端数据// 接收客户端数据:recv是阻塞调用,直到客户端发送数据或断开连接// 参数1:客户端socket;参数2:接收缓冲区;参数3:缓冲区大小(留1字节存'\0');参数4:0=默认模式int byte = recv(cilsockfd, buff, 127, 0);// 处理recv返回值(关键:判断客户端状态)if (byte <= 0) {// byte == 0 → 客户端主动关闭连接;byte < 0 → 接收失败(如网络异常)printf("client disconnected: cilsockfd=%d\n", cilsockfd);close(cilsockfd); // 关闭客户端socketexit(0); // 子进程完成使命,退出(避免子进程进入主循环accept新连接)}// 打印接收的数据printf("recv from client(%d): %s (bytes=%d)\n", cilsockfd, buff, byte);// 向客户端发送响应("ok")// send默认是阻塞的,返回发送的字节数(此处固定发送2字节:'o'和'k')send(cilsockfd, "ok", 2, 0);}}// -------------------------- 父进程逻辑(pid > 0)--------------------------else{// 父进程不需要与客户端通信,立即关闭客户端socket(父子进程共享文件描述符,子进程仍在使用)close(cilsockfd); // 父进程继续循环,等待下一个客户端连接(accept)continue;}}close(sockfd); // 主进程退出前关闭监听socket(实际不会执行,因主进程在while(1)中)return 0;
}

关键技术点解析

1. fork() 多进程的核心机制

  • fork() 调用后,操作系统会复制当前进程(父进程)的所有资源(代码、数据、文件描述符等),生成一个新进程(子进程);
  • 父子进程的唯一区别是 fork() 的返回值:父进程返回子进程的 PID(>0),子进程返回 0;
  • 父子进程共享文件描述符(如监听 socket、客户端 socket),因此子进程需关闭监听 socket,父进程需关闭客户端 socket,避免资源泄漏。

2. 字节序转换(htons/ntohs

  • 网络传输使用 “网络字节序”(大端序),而主机(如 x86 架构)使用 “主机字节序”(小端序);
  • htons(port):将主机字节序的端口号转为网络字节序(用于绑定端口);
  • ntohs(port):将网络字节序的端口号转为主机字节序(用于打印客户端端口)。

3. accept()/recv() 的阻塞特性

  • accept():主进程调用后会阻塞,直到有新客户端连接,返回客户端 socket 的文件描述符(cilsockfd);
  • recv():子进程调用后会阻塞,直到客户端发送数据(返回接收的字节数)或断开连接(返回 0)或出错(返回 <0)。

4. 资源释放的关键

  • 子进程必须关闭监听 socket(sockfd):子进程不需要监听新连接,关闭后避免端口被占用;
  • 父进程必须关闭客户端 socket(cilsockfd):父进程不处理客户端通信,关闭后释放文件描述符资源;
  • 客户端断开后,子进程必须 exit(0):避免子进程进入主循环的 accept(),导致多个进程监听同一个端口。

代码优化点(实际开发必备)

1. 处理僵尸进程(核心优化)

  • 问题:子进程退出后,若父进程未处理其退出状态,子进程会变成 “僵尸进程”(占用系统资源);

  • 解决方法:

    • 方法 1:父进程调用 waitpid(-1, NULL, WNOHANG) 非阻塞回收僵尸进程(可在主循环中定期调用);
    • 方法 2:注册 SIGCHLD 信号处理函数,子进程退出时触发信号,父进程在信号处理函数中回收。

    优化代码示例(添加信号处理):

    #include <signal.h>
    #include <sys/wait.h>// SIGCHLD信号处理函数:回收僵尸进程
    void sigchld_handler(int sig)
    {// waitpid(-1, NULL, WNOHANG):非阻塞回收所有子进程while (waitpid(-1, NULL, WNOHANG) > 0);
    }int main()
    {// 注册信号处理函数(在sock_init前)signal(SIGCHLD, sigchld_handler);// ... 其余代码不变
    }
    

2. 增加错误处理的详细信息

  • 原代码仅 exit(1),未打印错误原因,建议用 perror() 或 strerror(errno) 打印错误信息,方便调试。

3. 优化缓冲区与数据接收逻辑

  • 原代码缓冲区固定 128 字节,若客户端发送数据超过 127 字节,会被截断;
  • 建议循环 recv() 直到接收完所有数据(需约定数据长度或分隔符)。

4. 支持绑定任意 IP(而非仅 127.0.0.1)

  • 若需外部主机连接,将 saddr.sin_addr.s_addr = inet_addr("127.0.0.1") 改为 saddr.sin_addr.s_addr = INADDR_ANY(绑定所有网卡的 IP)。

5. 移除冗余头文件

  • 代码中包含 <pthread.h> 但未使用线程,可删除,减少编译依赖。

运行与测试步骤

1. 编译运行服务器

2. 客户端连接测试(用 telnet 或 nc

# 打开多个终端,每个终端执行:
telnet 127.0.0.1 6000
# 或
nc 127.0.0.1 6000# 连接后输入任意字符(如"hello"),服务器会返回"ok",并打印接收的内容

3. 现象观察

  • 每个客户端连接会触发服务器创建一个子进程;
  • 客户端断开后,子进程自动退出,且不会产生僵尸进程(优化后);
  • 多个客户端可同时连接,服务器能分别处理每个客户端的数据。

核心总结

  1. 多进程并发服务器的核心是 “主进程收连接,子进程处理通信”,通过 fork() 实现连接隔离;
  2. 关键细节:父子进程共享文件描述符,需各自关闭不需要的 socket;子进程退出后需回收,避免僵尸进程;
  3. 适用场景:连接数适中(几百以内)、每个连接的通信逻辑简单,进程隔离能提高稳定性;
  4. 对比选择:若连接数多(几千上万),建议用 IO 多路复用(select/poll/epoll)+ 线程池,或异步 IO,减少进程创建开销。

这份代码是 TCP 多进程服务器的 “最小可行版本”,理解其流程后,可基于优化点扩展为生产级服务器(如添加配置文件、日志系统、连接限制等)。

http://www.dtcms.com/a/614713.html

相关文章:

  • 网站建设找谁好深圳聘请做网站人员
  • C语言编译器Visual Studio | 高效开发与调试工具
  • 滨海新区建设和交通局网站一个人建设小型网站
  • Java 8 Lambda表达式详解
  • vip视频解析网站怎么做离石古楼角网站建设
  • DVL数据协议深度解析:PD0、PD4、PD6格式详解与实践应用
  • Web自动化测试详细流程和步骤
  • P1909 [NOIP 2016 普及组] 买铅笔
  • 萍乡网站开发公司k8s wordpress mysql
  • C++条件判断与循环(二)(算法竞赛)
  • 浏阳建设局网站广告电商怎么做
  • 微信朋友圈做网站推广赚钱吗网站建设费专票会计分录
  • 友元的作用与边界
  • 如何提高英语口语?
  • (6)框架搭建:Qt实战项目之主窗体快捷工具条
  • 做阿里云网站空间建设工程施工合同实例
  • web中间件——Tomcat
  • Linux中管理员和一般用户的用法小结
  • html手机网站模板html5网页设计教程
  • 【Mac】开发环境使用/维护
  • 网站代码设计惠州网站建设排名
  • 精美网站建设wordpress gae
  • 【STM32MP157 异核通信框架学习篇】(10)Linux下Remoteproc相关API (下)
  • 企业建站服务退役军人215专业品牌网站建设
  • 杭州模板网站建站做国外夏令营的网站
  • 基于SpringBoot的房屋租赁管理系统【协同过滤推荐算法+可视化统计+合同签署】
  • 【MySQL | 基础】函数
  • Java Set
  • (60页PPT)数据治理与数据安全防护方案(附下载方式)
  • DSAC-T算法实现控制倒立摆