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

相关推荐
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
小宇宙Zz2 天前
Maven依赖冲突
java·服务器·maven
网络研究院2 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展
酣大智2 天前
ARP代理--工作原理
运维·网络·arp·arp代理
treesforest2 天前
AI安全系统如何识别异常访问?IP风险识别正在成为关键能力
网络·人工智能·tcp/ip·安全·web安全
shushangyun_2 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
古城小栈2 天前
Unix 与 Linux 异同小叙
linux·服务器·unix
2601_961845152 天前
粉笔行测题库|系统班|刷题
网络·百度·微信·微信公众平台·facebook·新浪微博
程序猿阿伟2 天前
《Chrome离线扩展安装的底层逻辑与场景落地指南》
服务器·网络·chrome
凡人叶枫2 天前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++