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 生命周期详解

相关推荐
xixiaoyunya24 分钟前
内网穿透工具选型分析:从 ngrok 的局限性看国内开发场景下的替代方案
网络
Lonely 净土35 分钟前
Linux 运维文件写入操作
linux·运维·服务器·文件管理
城管不管36 分钟前
重生——第十一次面试之挖财一面2026.8.19已OC
java·服务器·jvm·数据库·spring·面试·职场和发展
FreeTinker1 小时前
远程查看的技术路径与工程选型分析
网络
liuyicenysabel2 小时前
多服务上线日记三:
运维·服务器·笔记·学习
吠品3 小时前
Wine 在 Linux 上运行 Windows 软件完整指南
java·linux·服务器
YXXY3134 小时前
网络基础2(三)
网络
m24tyy5 小时前
常见空间测绘搜索引擎介绍和使用以及子域名的收集
网络·测试工具·搜索引擎
数据安全技术观察5 小时前
数据动态脱敏:让脱敏策略跟着数据目录走
大数据·网络·数据库
我爱cope5 小时前
【计算机网络 | 网络层1:网络层的职责:IP 协议究竟解决了什么问题?】
网络·tcp/ip·计算机网络