第1章 网络游戏的开端:Echo
从服务端职责、Socket 端点、TCP/UDP 语义到可运行的 Echo 客户端与服务端,建立第一条端到端证据链。
从网络游戏的开端:Echo的核心问题开始
网络游戏不是从角色和场景开始,而是从一个能够被观察、被失败、被重放的双端连接开始。Echo 的价值不在于功能简单,而在于它把地址、端口、连接、收发和关闭压缩成最小闭环;这条闭环一旦含糊,后续所有协议都会继承同一种不确定性。
官方目录命中与来源边界
本页按照《Unity3D网络游戏实战(第2版)》公开目录覆盖下列单元。章与节名称按第二版登记;安全、幂等、可观测性和现代 Unity 线程边界属于教学补充,用于今天重现原机制,不冒充原书新增目录。
| # | 正式目录单元 | 本页逐项覆盖 |
|---|---|---|
| 1 | 1.1 藏在幕后的服务端 | 本节标题即公开目录单元 |
| 2 | 1.2 网络连接的端点:Socket | 1.2.1 Socket、1.2.2 IP地址、1.2.3 端口、1.2.4 Socket通信的流程、1.2.5 TCP和UDP协议 |
| 3 | 1.3 开始网络编程:Echo | 1.3.1 什么是Echo程序、1.3.2 编写客户端程序、1.3.3 客户端代码知识点、1.3.4 完成客户端、1.3.5 创建服务端程序、1.3.6 编写服务端程序、1.3.7 服务端知识点、1.3.8 测试Echo程序 |
| 4 | 1.4 更多API | 本节标题即公开目录单元 |
| 5 | 1.5 公网和局域网 | 本节标题即公开目录单元 |
公开目录的第一项是 1.1 藏在幕后的服务端。本页不把相邻章节或第一版目录并入当前章;跨章依赖会在实验中引用,但仍由其正式页面负责讲解和验收。
核心概念与因果关系
、、、、。 这些术语必须落到输入、状态、输出和失败证据:读者应能指出谁拥有状态、哪个边界拒绝非法输入、哪个版本或序号阻止旧消息,以及哪个指标证明队列没有把成本推给下一阶段。
| # | 机制 | 可观察解释 |
|---|---|---|
| 1 | 服务端是规则执行者 | 客户端发来的是请求,服务端才拥有连接表、会话状态和共享结果。即使 Echo 只是原样返回字节,也要明确谁监听、谁主动连接、谁负责关闭。 |
| 2 | 端点由地址与端口共同确定 | IP 决定主机或网卡,端口决定进程中的服务入口。绑定到回环地址只能本机访问,绑定到通配地址还要经过防火墙、NAT 和公网路由。 |
| 3 | TCP 是流,UDP 是数据报 | TCP 提供可靠有序字节流但不保存应用消息边界;UDP 保存一条数据报的边界,却可能丢失、重复或乱序。选择协议必须从业务容错开始。 |
| 4 | 生命周期比单次 API 更重要 | 创建、连接或绑定、收发、半关闭、完整关闭构成状态机。任何一步失败都要有明确错误分类和资源所有者。 |
| 5 | 公网可达性是独立证据 | 局域网成功只能证明本地路由有效,不能证明公网端口映射、云安全组和运营商网络均已放行。验收要分别保存两类轨迹。 |
服务端是规则执行者
客户端发来的是请求,服务端才拥有连接表、会话状态和共享结果。即使 Echo 只是原样返回字节,也要明确谁监听、谁主动连接、谁负责关闭。
验证时固定客户端版本、服务端版本、协议版本和输入脚本,先保存正常轨迹,再只改变一个边界条件。最终画面相同不代表过程正确;还要比较消息序号、状态版本、队列水位、拒绝原因和恢复动作。
端点由地址与端口共同确定
IP 决定主机或网卡,端口决定进程中的服务入口。绑定到回环地址只能本机访问,绑定到通配地址还要经过防火墙、NAT 和公网路由。
验证时固定客户端版本、服务端版本、协议版本和输入脚本,先保存正常轨迹,再只改变一个边界条件。最终画面相同不代表过程正确;还要比较消息序号、状态版本、队列水位、拒绝原因和恢复动作。
TCP 是流,UDP 是数据报
TCP 提供可靠有序字节流但不保存应用消息边界;UDP 保存一条数据报的边界,却可能丢失、重复或乱序。选择协议必须从业务容错开始。
验证时固定客户端版本、服务端版本、协议版本和输入脚本,先保存正常轨迹,再只改变一个边界条件。最终画面相同不代表过程正确;还要比较消息序号、状态版本、队列水位、拒绝原因和恢复动作。
生命周期比单次 API 更重要
创建、连接或绑定、收发、半关闭、完整关闭构成状态机。任何一步失败都要有明确错误分类和资源所有者。
验证时固定客户端版本、服务端版本、协议版本和输入脚本,先保存正常轨迹,再只改变一个边界条件。最终画面相同不代表过程正确;还要比较消息序号、状态版本、队列水位、拒绝原因和恢复动作。
公网可达性是独立证据
局域网成功只能证明本地路由有效,不能证明公网端口映射、云安全组和运营商网络均已放行。验收要分别保存两类轨迹。
验证时固定客户端版本、服务端版本、协议版本和输入脚本,先保存正常轨迹,再只改变一个边界条件。最终画面相同不代表过程正确;还要比较消息序号、状态版本、队列水位、拒绝原因和恢复动作。
六阶段证据链
可复现实验
建立一个最小 Echo 客户端和服务端。先预测 localhost 成功是否意味着另一台机器一定可达,再分别使用回环地址、局域网地址、错误端口和中途断线四组输入;记录连接状态、系统错误码、已收字节数和关闭方向。
最小状态机
public enum EvidenceStage {
"声明双端职责",
"选择地址端口",
"创建并连接 Socket",
"循环收发字节",
"分类关闭与异常",
"跨网络复测签发"
}
public sealed record TraceEvent(
string TraceId,
EvidenceStage Stage,
long Sequence,
string State,
string Decision);边界驱动器
var samples = new[] { "baseline", "boundary", "failure" };
foreach (var sample in samples) {
ResetFixture(seed: 20260716);
RunScenario(sample);
AssertSingleOwner();
AssertBoundedQueues();
ExportTrace(sample);
}证据契约
trace_id,client_seq,server_seq,state_version,queue_depth,first_divergence,decision
baseline-01,42,42,7,3,none,pass
boundary-01,43,43,8,31,none,pass
failure-01,44,43,8,32,循环收发字节,reject实现代码只是实验的一部分。每次运行还要保存客户端与服务端版本、协议摘要、随机种子、样本类型、首偏离节点、恢复动作和最终决策。若相同输入不能重放,结论不能进入交付清单。
两个必须主动制造的失败
验收矩阵
| 维度 | 正常样本 | 边界样本 | 失败样本 | 通过条件 |
|---|---|---|---|---|
| 输入 | 固定合法流程 | 时序、容量或重复临界值 | 畸形、断线或旧代际 | 分类清晰且可重放 |
| 状态 | 单调推进 | 达到预算但不越界 | 在首偏离点停止 | 单一所有者、无隐式写入 |
| 输出 | 双端证据一致 | 退化可解释 | 拒绝、补偿或回滚 | 不产生第二份事实 |
| 恢复 | 下一流程正常 | 压力下降后收敛 | 同输入修复后通过 | 无幽灵状态和重复结果 |
本页签发不变量是:每个连接只有一个明确所有者;任意分片下 Echo 字节完全一致;连接失败和远端关闭不会污染下一次会话。 任何截图、单次演示或平均值都不能替代它。
练习
小结
- 服务端是规则执行者:客户端发来的是请求,服务端才拥有连接表、会话状态和共享结果。即使 Echo 只是原样返回字节,也要明确谁监听、谁主动连接、谁负责关闭。
- 端点由地址与端口共同确定:IP 决定主机或网卡,端口决定进程中的服务入口。绑定到回环地址只能本机访问,绑定到通配地址还要经过防火墙、NAT 和公网路由。
- TCP 是流,UDP 是数据报:TCP 提供可靠有序字节流但不保存应用消息边界;UDP 保存一条数据报的边界,却可能丢失、重复或乱序。选择协议必须从业务容错开始。
- 生命周期比单次 API 更重要:创建、连接或绑定、收发、半关闭、完整关闭构成状态机。任何一步失败都要有明确错误分类和资源所有者。
- 公网可达性是独立证据:局域网成功只能证明本地路由有效,不能证明公网端口映射、云安全组和运营商网络均已放行。验收要分别保存两类轨迹。
- 已用正常、边界和失败样本验证“每个连接只有一个明确所有者;任意分片下 Echo 字节完全一致;连接失败和远端关闭不会污染下一次会话。”。
- 已把第二版正式目录、第一版差异和现代教学补充分开记录。