第1章 网络编程基础
从 MMORPG 客户端/服务器职责、OSI 与 TCP/IP,到 Socket 生命周期、字节序、TCP 字节流和 UDP 数据报,重建可验证的通信地基。
从第1章的核心问题开始
网络程序的第一条纪律是区分传输事实与业务消息。TCP 只提供有序字节流,不提供消息边界;UDP 保留数据报边界,却不保证到达和顺序。服务器必须先定义权威职责、编码规则和连接生命周期,再选择传输并实现组帧。
官方目录命中与来源边界
本页明确覆盖:网络游戏服务器端开发概述、网络游戏类型、MMORPG服务器与客户端功能划分、网络通信协议、网络协议、OSI通信协议模型、TCP/IP协议、TCP/IP协议族、Socket编程原理、套接字、Socket通信流程、Socket函数、IP地址转换、字节转换、基本Socket通信、TCP/IP通信实现、UDP/IP通信实现。这些名称来自 2007 年版公开书目。公开目录没有披露的细分内容会标为教学展开;现代 API、安全要求和部署方法用于帮助今天的读者验证原机制,不代表原书在 2007 年已经使用这些实现。
| # | 机制或证据边界 | 可观察解释 |
|---|---|---|
| 1 | 职责先于接口 | MMORPG 客户端负责输入与表现,服务器负责身份、规则、共享世界和持久结果。客户端发来的是请求,不能直接成为权威状态。 |
| 2 | 分层用于定位 | OSI 模型帮助判断故障属于链路、网络、传输还是应用语义;TCP/IP 协议族把 IP 寻址、TCP 可靠流、UDP 数据报等能力组合起来。 |
| 3 | Socket 生命周期 | 服务端依次创建套接字、绑定地址、监听、接受连接、收发并关闭;每一步都可能部分成功,错误路径必须释放唯一所有者持有的资源。 |
| 4 | 表示必须统一 | IP 文本与二进制地址、主机字节序与网络字节序、定长头与变长体都要有单一转换边界,不能把本机结构体直接发送到网络。 |
| 5 | 传输语义不同 | TCP 接收可能得到半包或多包,必须按长度循环组帧;UDP 一次接收对应一个数据报,但需要序号、丢包策略和最大报文限制。 |
核心概念与因果关系
、、、、。这些概念不能只停留在定义上:每个概念都要落到输入、内部状态、输出和失败证据。读者应能指出哪个模块拥有状态、哪个边界拒绝非法输入、哪个指标证明系统没有把成本推给下游。
职责先于接口
MMORPG 客户端负责输入与表现,服务器负责身份、规则、共享世界和持久结果。客户端发来的是请求,不能直接成为权威状态。
验证时固定软件版本、配置和输入规模,先保存正常轨迹,再只改变一个边界条件。最终结果相同并不代表过程正确;必须比较阶段状态、队列水位、错误分类和恢复动作。
分层用于定位
OSI 模型帮助判断故障属于链路、网络、传输还是应用语义;TCP/IP 协议族把 IP 寻址、TCP 可靠流、UDP 数据报等能力组合起来。
验证时固定软件版本、配置和输入规模,先保存正常轨迹,再只改变一个边界条件。最终结果相同并不代表过程正确;必须比较阶段状态、队列水位、错误分类和恢复动作。
Socket 生命周期
服务端依次创建套接字、绑定地址、监听、接受连接、收发并关闭;每一步都可能部分成功,错误路径必须释放唯一所有者持有的资源。
验证时固定软件版本、配置和输入规模,先保存正常轨迹,再只改变一个边界条件。最终结果相同并不代表过程正确;必须比较阶段状态、队列水位、错误分类和恢复动作。
表示必须统一
IP 文本与二进制地址、主机字节序与网络字节序、定长头与变长体都要有单一转换边界,不能把本机结构体直接发送到网络。
验证时固定软件版本、配置和输入规模,先保存正常轨迹,再只改变一个边界条件。最终结果相同并不代表过程正确;必须比较阶段状态、队列水位、错误分类和恢复动作。
传输语义不同
TCP 接收可能得到半包或多包,必须按长度循环组帧;UDP 一次接收对应一个数据报,但需要序号、丢包策略和最大报文限制。
验证时固定软件版本、配置和输入规模,先保存正常轨迹,再只改变一个边界条件。最终结果相同并不代表过程正确;必须比较阶段状态、队列水位、错误分类和恢复动作。
六阶段证据链
可复现实验
写一个最小回显服务器和测试客户端。先预测一次 send 是否必然对应一次 recv,再分别注入 1 字节分片、两个帧合并、客户端中途断开和 UDP 序号跳变;记录解析器缓冲区、期望长度和第一处拒绝原因。
最小实现
struct FrameHeader { std::uint32_t bodyBytes; std::uint16_t type; };
bool decodeHeader(std::span<const std::byte> raw, FrameHeader& out) {
if (raw.size() < 6) return false;
out.bodyBytes = readNetworkU32(raw.subspan(0, 4));
out.type = readNetworkU16(raw.subspan(4, 2));
return out.bodyBytes <= 64 * 1024;
}边界处理
bool readFrame(Socket& socket, Buffer& pending, Message& out) {
while (!pending.hasCompleteFrame()) {
const auto n = socket.receive(pending.writeSpan());
if (n == 0) return false;
pending.commit(n);
}
return pending.popFrame(out);
}证据契约
struct Datagram { std::uint32_t session; std::uint32_t sequence; Bytes body; };
Decision accept(const Datagram& packet, ReplayWindow& window) {
if (packet.body.size() > 1200) return Decision::RejectOversize;
if (!window.markIfNew(packet.sequence)) return Decision::RejectReplay;
return Decision::Dispatch;
}实现代码只是实验的一部分。每次运行还要记录构建版本、配置摘要、样本类型、输入规模、首偏离节点、恢复动作和最终决策。若无法用相同输入重放,结论不能进入交付清单。
两个必须主动制造的失败
验收矩阵
| 维度 | 正常样本 | 边界样本 | 失败样本 | 通过条件 |
|---|---|---|---|---|
| 输入 | 合法且固定 | 容量、顺序或时序临界值 | 畸形、重复或中断 | 分类清晰且可重放 |
| 状态 | 按六阶段推进 | 达到预算但不越界 | 在首偏离节点停止 | 无隐式跨层写入 |
| 输出 | 结果和证据一致 | 退化可解释 | 拒绝、补偿或回滚 | 不变量恢复 |
| 交接 | 他人按记录复验 | 干净环境复验 | 删除隐含依赖 | 结论仍成立 |
本页签发不变量是:任何分片组合都只能产生完整且有界的消息;无效长度、失效地址、重复数据报和断开连接不会污染下一条会话。 任何演示、截图或单次成功都不能替代它。
练习
小结
- 职责先于接口:MMORPG 客户端负责输入与表现,服务器负责身份、规则、共享世界和持久结果。客户端发来的是请求,不能直接成为权威状态。
- 分层用于定位:OSI 模型帮助判断故障属于链路、网络、传输还是应用语义;TCP/IP 协议族把 IP 寻址、TCP 可靠流、UDP 数据报等能力组合起来。
- Socket 生命周期:服务端依次创建套接字、绑定地址、监听、接受连接、收发并关闭;每一步都可能部分成功,错误路径必须释放唯一所有者持有的资源。
- 表示必须统一:IP 文本与二进制地址、主机字节序与网络字节序、定长头与变长体都要有单一转换边界,不能把本机结构体直接发送到网络。
- 已用正常、边界和失败样本验证“任何分片组合都只能产生完整且有界的消息;无效长度、失效地址、重复数据报和断开连接不会污染下一条会话。”。
- 已把公开目录、历史语境和现代教学展开分开记录。