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

TCP(2)

TCP

多进程版本

这段代码实现了一个 多进程版本的 TCP Echo 服务器,主要功能是接收客户端连接,并使用 父子进程模型 处理多个客户端请求。以下是详细解析:


1. 核心功能

(1) 初始化服务器 (Init())

  1. 创建监听 Socket

    _listensockfd = socket(AF_INET, SOCK_STREAM, 0);  // IPv4 + TCP
    
    • 失败时记录 FATAL 日志并退出。
  2. 绑定端口 (bind())

    InetAddr local(_port);  // 封装 sockaddr_in
    bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
    
    • 默认绑定 0.0.0.0(所有网卡)。
  3. 监听连接 (listen())

    listen(_listensockfd, backlog);  // backlog=8(等待连接队列长度)
    

(2) 处理客户端请求 (Service())

  • 读取客户端数据

    ssize_t n = read(sockfd, buffer, sizeof(buffer)-1);
    
    • 成功时回显 "echo#"+数据
    • n=0:客户端断开连接。
    • n<0:读取异常,关闭连接。
  • 回显数据

    write(sockfd, echo_string.c_str(), echo_string.size());
    

(3) 启动服务器 (Run())

  1. 接受客户端连接 (accept())

    int sockfd = accept(_listensockfd, CONV(peer), &len);
    
    • 失败时记录 WARNING 并继续循环。
  2. 创建子进程处理连接

    pid_t id = fork();
    if (id == 0) {  // 子进程close(_listensockfd);  // 关闭不需要的监听socketif (fork() > 0) exit(OK);  // 孙子进程脱离父进程Service(sockfd, addr);     // 孙子进程处理请求exit(OK);
    } else {  // 父进程close(sockfd);  // 父进程关闭客户端socketwaitpid(id, nullptr, 0);  // 等待子进程结束(避免僵尸进程)
    }
    
    • fork() 技巧:避免僵尸进程,孙子进程由 init 托管。

2. 多进程模型解析

(1) 进程关系

父进程(主循环)├─ 子进程(立即退出)└─ 孙子进程(实际处理请求,由 init 回收)
  • 目的:避免僵尸进程,无需显式 wait()

(2) 文件描述符管理

进程关闭的 fd保留的 fd
父进程sockfd(客户端)_listensockfd
子进程_listensockfdsockfd
孙子进程sockfd

3. 关键优化点

(1) 避免僵尸进程

  • 子进程立即退出,孙子进程由 init 回收:
    if (fork() > 0) exit(OK);  // 子进程退出,孙子进程成为孤儿进程
    

(2) 资源释放

  • 父进程:关闭 sockfd(避免泄漏)。
  • 子进程:关闭 _listensockfd(不需要监听)。

(3) 错误处理

  • 所有系统调用检查返回值,记录日志。

4. 潜在问题

(1) 缓冲区溢出风险

read(sockfd, buffer, sizeof(buffer)-1);  // 正确
buffer[n] = '\0';  // 应添加此句确保字符串终止
  • 当前代码 buffer[0]=0 会清空数据,应改为 buffer[n]='\0'

(2) 高并发限制

  • 多进程模型 资源消耗大,适合低并发场景。
  • 改进方案:
    • 线程池pthread + 任务队列)。
    • IO 多路复用epoll)。

(3) 端口复用

  • 服务器崩溃后可能无法立即重启(TIME_WAIT 状态)。
  • 解决方案:
    int opt = 1;
    setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    

5. 改进建议

(1) 增加 SO_REUSEADDR

int opt = 1;
setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

(2) 修复 read()/write() 错误处理

if (write(sockfd, echo_string.c_str(), echo_string.size()) < 0) {LOG(LogLevel::WARNING) << "write error";break;
}

(3) 支持优雅退出

void Stop() { _isrunning = false; }  // 外部调用可停止服务器

总结

  • 功能:多进程 TCP Echo 服务器,支持并发客户端。
  • 优点
    • 避免僵尸进程(双 fork 技巧)。
    • 父子进程正确管理文件描述符。
  • 缺点
    • 进程创建开销大,不适合高并发。
    • 缓冲区处理不够安全。
  • 适用场景:学习多进程网络编程,低并发 Echo 服务。

多线程版本

这段代码实际上是一个 多线程版本 的 TCP 服务器(使用了线程池),但注释中保留了 多进程版本 的实现(被注释掉的 fork() 部分)。我来解释 多进程版本 的设计含义和实现逻辑:


1. 多进程版本的核心思想

多进程服务器通过 fork() 创建子进程来处理客户端连接,每个客户端由一个独立的进程服务。
优点

  • 进程间隔离性强,一个进程崩溃不会影响其他进程
  • 适合 CPU 密集型任务(Python 的 GIL 限制就是典型例子)
  • 编程模型相对简单(相比多线程)

缺点

  • 进程创建/销毁开销大(比线程重)
  • 进程间通信(IPC)较复杂(需要管道、共享内存等)
  • 大量并发时系统资源消耗大

2. 代码中的多进程实现(注释部分)

pid_t id = fork();
if (id < 0) {LOG(LogLevel::FATAL) << "fork error";exit(FORK_ERR);
}
else if (id == 0) {  // 子进程close(_listensockfd);  // 子进程不需要监听socketif (fork() > 0) exit(OK);  // 孙子进程脱离父进程Service(sockfd, addr);     // 孙子进程处理请求exit(OK);
}
else {  // 父进程close(sockfd);  // 父进程不需要客户端socketpid_t rid = waitpid(id, nullptr, 0);  // 等待子进程结束(void)rid;
}

关键点解析

  1. fork() 技巧

    • 第一次 fork() 创建子进程,子进程再 fork() 创建孙子进程后立即退出。
    • 目的:让孙子进程被 init 进程(PID=1)接管,避免僵尸进程。
  2. 文件描述符管理

    进程关闭的 fd保留的 fd
    父进程sockfd(客户端)_listensockfd
    子进程_listensockfdsockfd
    孙子进程sockfd
  3. 避免僵尸进程

    • 父进程通过 waitpid() 回收子进程(第一次 fork() 的进程)。
    • 孙子进程由 init 自动回收,无需处理。

3. 多进程 vs 多线程(代码对比)

特性多进程版本当前多线程版本
并发模型每个客户端一个进程线程池处理多个客户端
资源隔离性高(进程间内存隔离)低(线程共享内存)
上下文切换开销较大较小
编程复杂度中等(需处理 fork() 和 IPC)较低(线程间共享数据方便)
适用场景CPU 密集型任务I/O 密集型任务

4. 如何恢复多进程版本?

如果想恢复多进程实现,只需:

  1. 取消注释 fork() 部分的代码。
  2. 注释掉线程池相关的代码:
    // ThreadPool<task_t>::GetInstance()->Enqueue([this,&sockfd,&addr](){
    //     this->Service(sockfd,addr);
    // });
    

5. 多进程版本的改进建议

  1. 信号处理
    取消注释 signal(SIGCHLD, SIG_IGN),避免僵尸进程:

    signal(SIGCHLD, SIG_IGN);  // 忽略子进程退出信号
    
  2. 端口复用
    添加 SO_REUSEADDR 选项,避免 TIME_WAIT 状态:

    int opt = 1;
    setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
  3. 进程池优化
    类似线程池,可以预创建进程池(但实现复杂,通常直接用多线程)。


总结

  • 多进程版本 通过 fork() 为每个客户端创建独立进程,适合 CPU 密集型 任务。
  • fork() 技巧 用于避免僵尸进程。
  • 当前代码实际是 多线程+线程池 实现,更适合 高并发 I/O 密集型 场景。
  • 两种模式各有优劣,选择取决于具体需求。
http://www.dtcms.com/a/314918.html

相关文章:

  • IP 成长的破局之道:从停滞到突破的核心逻辑
  • MyBatis高效查询:简化JDBC开发实战
  • Python 虚拟环境深入浅出全指南
  • 为流媒体时代而生的云服务:Akamai 推出 Accelerated Compute 加速计算服务
  • 在 Maven 多模块项目中统一管理配置文件
  • 8.4 Java Web(Maven P50-P57)
  • 【web应用】为什么 子模块已经删除,但 Maven 依赖项仍然报错?
  • python采集拍立淘按图搜索API接口,json数据参考
  • maven install和package的区别
  • AI绘图-Stable Diffusion-WebUI的基本用法
  • 安卓开发--ConstraintLayout(约束布局)
  • 5天挑战网络编程 -DAY1(linux版)
  • 二十年代深度立体匹配的进化与前沿综述
  • 咖啡参考基因组
  • 机器学习 入门——决策树分类
  • FreeRTOS源码分析四:时钟中断处理响应流程
  • 倒排索引:Elasticsearch 搜索背后的底层原理
  • 【C#】Blazor基本应用
  • ICCV2025 Tracking相关paper汇总和解读(19篇)
  • Matlab(2)
  • Maven配置,Idea集成Maven_依赖引入,Idea生成单元测试
  • 《AI Agent工程师(初级)》
  • ubuntu24.01安装odoo18
  • gdb print设置技巧,离线查看复杂结构体和数组变量内容,展开多层嵌套的结构体的方法
  • 【Docker安装】Ubuntu 24.04.2 LTS系统下安装Docker环境——指定APT源安装方式
  • C + +
  • 宝塔运维实战:CentOs7启动PHP7.4失败
  • 华为OD机考2025C卷 - 最小矩阵宽度(Java Python JS C++ C )
  • Linux下PXE服务器搭建
  • 嵌入式数据结构笔记(二):内存工具与链表操作