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

相关推荐
躺不平的理查德1 小时前
Shell逻辑判断备忘录
运维·服务器·git
skywalk81631 小时前
Trae生成的中文编程语言关键字(如“定“、“函“、“印“等)需要和标识符之间用 空格 隔开,以确保正确识别
服务器·开发语言·编程
焦糖玛奇朵婷2 小时前
健身房预约小程序开发、设计
java·大数据·服务器·前端·小程序
小新同学^O^2 小时前
简单学习 --> TCP协议
java·网络·tcp
其实防守也摸鱼2 小时前
软件安全与漏洞--软件安全设计
运维·网络·安全·网络安全·密码学·需求分析·软件安全
Liangwei Lin2 小时前
LeetCode 76. 最小覆盖子串
运维·服务器
一只数据集2 小时前
NVIDIA Nemotron AIQ Agentic Safety Dataset:面向企业级智能体系统的安全与防护评估数据集全面解析
网络·数据库·安全
你的保护色2 小时前
软件定义网络SDN
网络
Mortalbreeze3 小时前
深度理解进程----进程状态
linux·运维·服务器