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

相关推荐
AI直播技术杂谈13 小时前
九点整那场,讲品顺序是前一天下午就锁定的
网络·python·ai·秒播
~城南花已开~13 小时前
Intel E810 SR-IOV VF Spoofcheck 防伪造测试-KVM3
服务器·网络·笔记·学习·ubuntu·etl工程师
乌恩大侠13 小时前
Supermicro 发布六款 NVIDIA ARC 服务器:从 1U 边缘基站到 4U 八卡 AI-RAN 平台,全面对比
运维·服务器·人工智能
80s77714 小时前
态住宅IP在跨境电商防关联中的技术应用
网络·网络协议·tcp/ip
XiYang-DING14 小时前
【WebSocket】文本帧Text Frame
网络·websocket·网络协议
wacsii14 小时前
【Linux基础开发工具(五)】Git版本控制器从入门到实战:核心操作三板斧与新手避坑指南
linux·运维·服务器
Novproxy_Z14 小时前
代理 IP 黑白名单体系搭建:自动标记风控 IP,减少封号
网络·网络协议·tcp/ip
FII工业富联科技服务14 小时前
服务器开始按柜交付 数据中心需要具备怎样的承接能力
大数据·运维·服务器·人工智能·自动化·制造
山东孙燕姿14 小时前
设备台账重构:从一张表到三层模型
android·java·网络·低代码·重构·rxjava
嵌入式@hxydj14 小时前
WorkBuddy中调用本地ComfyUi工作流免费生图
运维·服务器