TCP Echo Server 深度解析:从单进程到线程池的演进之路(上)

1. 整体架构概览


2. Common.hpp ------ 公共基础设施

复制代码
#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <sys/socket.h> //网络服务 - 四剑客
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

enum ExitCode
{
    OK = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERR
};

class NoCopy
{
public:
    NoCopy() {}
    ~NoCopy() {}
    NoCopy(const NoCopy &) = delete;
    const NoCopy &operator=(const NoCopy &) = delete;
};

#define CONV(addr) ((struct sockaddr*)&addr)

2.1 为什么需要 Common.hpp?

复制代码
enum ExitCode
{
    OK = 0,           // 正常退出
    USAGE_ERR,        // 命令行参数错误
    SOCKET_ERR,       // socket 创建失败
    BIND_ERR,         // bind 绑定失败
    LISTEN_ERR,       // listen 监听失败
    CONNECT_ERR,      // connect 连接失败
    FORK_ERR          // fork 创建子进程失败
};

核心问题:为什么不直接用 exit(2) 硬编码?

硬编码方式 使用枚举
exit(2) exit(SOCKET_ERR)
数字含义不明 语义清晰,见名知意
多处使用易出错 统一维护,修改一处全局生效
调试困难 日志可直接打印错误名称

2.2 NoCopy 类 ------ 禁止拷贝的优雅实现

复制代码
class NoCopy
{
public:
    NoCopy() {}
    ~NoCopy() {}
    NoCopy(const NoCopy &) = delete;              // 删除拷贝构造
    const NoCopy &operator=(const NoCopy &) = delete;  // 删除赋值运算符
};

为什么服务器要禁止拷贝?

服务器持有核心的资源:

  1. _listensockft --- 监听套接字fd

  2. _port --- 绑定的端口号

  3. _isrunning --- 运行状态标志

如果允许拷贝 :

TcpServer s1(8080);

TcpServer s2 = s1; //浅拷贝

s1.~TcpServer() --> close(fd)

s2 中fd变成悬空描述符

二次close可能导致其他文件描述符被关闭

继承 NoCopy 的优雅之处:

复制代码
class TcpServer : public NoCopy  // 一行代码,禁止所有拷贝行为
{
    // ...
};

"服务器往往是禁止拷贝的" ------ 可以把拷贝构造设置为私有的!优雅一点的写法:来一个设计,定一个 NoCopy 的类。

2.3 CONV 宏 ------ 地址类型转换

复制代码
#define CONV(addr) ((struct sockaddr*)&addr)

3. InetAddr.hpp ------ 网络地址封装

3.1 字节序问题

3.2 三种构造场景

复制代码
// 场景1:从已有 sockaddr_in 构造(accept 后使用)
InetAddr(struct sockaddr_in &addr) : _addr(addr)
{
    _port = ntohs(_addr.sin_port);           // 网络序 → 主机序
    char ipbuffer[64];
    inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
    _ip = ipbuffer;                          // 网络字节 → 点分十进制字符串
}

// 场景2:从 IP 字符串 + 端口号构造(客户端连接服务器)
InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port)
{
    memset(&_addr, 0, sizeof(_addr));
    _addr.sin_family = AF_INET;
    inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);  // 字符串 → 网络字节
    _addr.sin_port = htons(port);                      // 主机序 → 网络序
}

// 场景3:仅指定端口号(服务器绑定,IP 使用 INADDR_ANY)
InetAddr(uint16_t port) : _port(port), _ip()
{
    memset(&_addr, 0, sizeof(_addr));
    _addr.sin_family = AF_INET;
    _addr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0 ------ 监听所有网卡
    _addr.sin_port = htons(port);
}

INADDR_ANY (0.0.0.0) 的含义


4. NoCopy 设计哲学

4.1 C++98 的笨拙做法

复制代码
// 旧时代:将拷贝构造声明为 private,但不实现
class OldTcpServer {
private:
    TcpServer(const TcpServer&);              // 只声明,不实现
    TcpServer& operator=(const TcpServer&);     // 只声明,不实现
public:
    // ...
};
// 问题:友元类仍可访问,链接时才发现错误

4.2 C++11 的优雅做法

复制代码
// 新时代:= delete 显式删除
class NoCopy {
public:
    NoCopy(const NoCopy &) = delete;           // 编译期报错,清晰明确
    const NoCopy &operator=(const NoCopy &) = delete;
};

class TcpServer : public NoCopy {  // 一行继承,功能完备
    // ...
};

设计模式:Mixin 风格


5. TcpServer 生命周期详解

相关推荐
C+++Python29 分钟前
BIO、NIO、AIO 区别
网络·nio
VOOHU-沃虎1 小时前
沃虎——网络变压器与RJ45集成连接器选型实战:从百兆到10G、从非PoE到4PPoE
网络
2301_773643622 小时前
华为云存储实验
网络·mysql·华为云
迷枫7122 小时前
DM8 目录结构与常用排查入口梳理
服务器·数据库
ylscode2 小时前
Windows 内核惊现高危提权漏洞 CVE-2026-40369:沙箱隔离失效,SYSTEM 权限唾手可得
网络·安全·安全威胁分析
jieyu11192 小时前
Wireshark使用指南【超全面】
网络·wireshark
weixin_520649872 小时前
通信【报文】
网络
志栋智能2 小时前
小步快跑:从单一场景开启超自动化巡检之旅
运维·网络·人工智能·自动化
XINERTEL3 小时前
视频卡顿花屏?专业视频质量评估测试让画质从“凭感觉”到“数据说话”
网络·测试工具·音视频·丢包
AugustRed3 小时前
Linux 运维常用命令大全(超全速查表)
运维·网络·php