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; // 删除赋值运算符
};
为什么服务器要禁止拷贝?
服务器持有核心的资源:
_listensockft --- 监听套接字fd
_port --- 绑定的端口号
_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 生命周期详解
