在很多 Unity 项目中,网络通信一开始看起来并不复杂。

客户端连接服务器,发送一条消息,服务器返回一条消息,然后根据返回结果刷新界面。

对于小 Demo 来说,这样就够了。

但是一旦项目变成长期维护的商业项目,尤其是 RPG、MMO、SLG、模拟经营 这类系统比较多的游戏,网络通信就会变成整个项目中非常核心的一层。

因为很多游戏逻辑最终都会落到网络通信上:

  • 登录

  • 角色数据

  • 背包数据

  • 战斗请求

  • 技能释放

  • 聊天

  • 好友

  • 邮件

  • 商城

  • 任务

  • 活动

  • 多人同步

  • 状态更新

如果网络层设计得不好,项目后期会非常痛苦。

所以在 MyFramework 中,我没有把网络通信只当成一个简单的 Socket 封装,而是把它作为客户端和服务器协作工具链的一部分来设计。

项目地址:

https://github.com/ZHOURUIH/MyFramework

配套服务器框架:

https://github.com/ZHOURUIH/MyServerFramework

这篇文章主要聊一下:

  • 为什么 Unity 项目需要自己的网络通信层

  • 网络层真正难的地方是什么

  • 协议设计为什么重要

  • 为什么要做消息代码生成

  • 客户端和服务器如何保持协议一致

  • 网络通信如何和框架其他模块配合

  • 一个长期项目中的网络层应该关注哪些问题

一、为什么要自己封装网络层

很多项目刚开始做网络通信时,可能只是简单封装一下 Socket。

比如:

  • 连接服务器

  • 发送字节流

  • 接收字节流

  • 解析消息

  • 回调业务逻辑

从技术上看,这些事情并不复杂。

但真实项目中的网络层很少这么简单。

一个长期维护的游戏项目,网络层至少要考虑这些问题:

  • 消息协议如何定义

  • 消息 ID 如何管理

  • 客户端和服务器字段如何保持一致

  • 消息如何序列化和反序列化

  • 收到消息后如何派发到对应模块

  • 网络断开后如何重连

  • 消息异常时如何定位

  • 协议修改后如何避免遗漏

  • 是否支持不同通信方式

  • 是否方便调试和扩展

如果这些问题都散落在业务代码里,后期一定会很难维护。

所以我认为网络层不应该只是“能发消息”,还应该是一个稳定的工程基础设施。

MyFramework 中的网络模块主要目标不是封装一个 Socket,而是让客户端和服务器通信变得更可控、更统一、更容易维护。

二、网络层最重要的是协议一致性

客户端和服务器通信时,最怕的不是连接失败,而是双方对同一条消息的理解不一致。

比如:

客户端认为某条消息里有这些字段:

  • playerID

  • itemID

  • count

但服务器认为这条消息里是:

  • playerID

  • count

  • itemID

字段顺序不一致,就可能导致反序列化错误。

再比如客户端新增了一个字段,但服务器没有同步修改。

或者服务器修改了字段类型,客户端仍然使用旧类型。

这些问题有时候不会立刻崩溃,而是产生非常奇怪的逻辑错误。

所以网络层首先要解决的问题是:

客户端和服务器必须基于同一份协议定义。

这也是我做协议代码生成的重要原因。

如果客户端和服务器各自手写消息类,长期维护时很容易不一致。

而如果协议结构由工具统一生成,就可以大幅减少这类错误。

三、为什么要做消息代码生成

在游戏项目中,网络消息数量会越来越多。

刚开始可能只有几条:

  • 登录请求

  • 登录返回

  • 心跳

  • 拉取角色数据

后面会越来越多:

  • 背包消息

  • 技能消息

  • 战斗消息

  • 任务消息

  • 商城消息

  • 邮件消息

  • 好友消息

  • 聊天消息

  • 活动消息

  • 多人同步消息

如果每条消息都手写,问题会越来越明显。

1. 重复代码太多

每条消息通常都需要:

  • 定义字段

  • 写序列化

  • 写反序列化

  • 写消息 ID

  • 写注册逻辑

  • 写客户端类

  • 写服务器类

这些代码大部分都是结构性代码。

它们本身没有太多业务含义,但非常容易写错。

所以这类代码非常适合生成。

2. 修改协议容易漏改

如果协议字段变化,需要同时修改客户端和服务器。

如果是手写代码,就很容易出现只改了一端的情况。

例如:

  • 客户端字段改了,服务器没改

  • 服务器字段顺序改了,客户端没改

  • 客户端类型改了,服务器仍然是旧类型

  • 注册表里忘记添加新消息

通过协议生成工具,可以让这些结构性代码由工具统一生成,减少人工同步错误。

3. 消息注册可以自动化

网络消息通常需要根据消息 ID 创建对应消息对象。

如果注册逻辑全部手写,很容易漏注册。

所以消息注册代码也适合生成。

例如协议工具可以根据消息定义自动生成:

  • 消息 ID 常量

  • 消息类

  • 消息注册函数

  • 客户端消息表

  • 服务器消息表

这样新增消息后,只需要重新生成代码即可。

4. 手写逻辑应该保留

代码生成并不是要覆盖所有逻辑。

网络消息中仍然会有一些手写逻辑,例如:

  • 收到消息后的业务处理

  • 消息发送前的数据组织

  • 特殊日志

  • 调试辅助

  • 某些模块自己的封装接口

所以工具生成的应该是结构性代码,而不是业务逻辑。

MyFramework 的整体思路一直是:

工具生成重复结构,程序编写业务逻辑。

这点在 UI 代码生成、配置表代码生成、协议代码生成中都是一致的。

四、消息收发流程应该清晰

网络通信的流程必须清晰。

否则一旦出现问题,很难定位消息到底卡在哪一步。

一个比较清晰的流程应该是:

  • 业务模块发起请求

  • 创建对应消息对象

  • 写入字段

  • 序列化成字节数据

  • 通过网络连接发送

  • 服务器接收并解析

  • 服务器处理逻辑

  • 服务器返回消息

  • 客户端接收字节数据

  • 反序列化成消息对象

  • 根据消息 ID 派发给对应模块

  • 业务模块刷新状态或界面

这里面每一步都可能出问题。

例如:

  • 消息没有发送出去

  • 连接已经断开

  • 消息 ID 不存在

  • 字段解析失败

  • 收到消息但没有注册处理函数

  • 业务处理顺序不对

  • UI 已经关闭但消息才回来

所以网络层需要提供清晰的收发流程,而不是让业务代码到处直接操作 Socket。

MyFramework 中,网络层会尽量把底层通信、消息解析、消息注册、消息派发这些流程统一管理。

业务层只关心:

我要发送什么消息。

我收到什么消息后该做什么。

而不是关心底层字节流如何处理。

五、网络通信不能只考虑正常情况

很多网络层 Demo 只演示正常情况。

比如连接成功、发送成功、收到返回。

但真实项目中,异常情况非常多。

例如:

  • 网络断开

  • 服务器主动断开

  • 连接超时

  • 消息超时

  • 服务器返回错误码

  • 客户端切后台

  • 弱网环境

  • 重连后状态不一致

  • 重复收到消息

  • 消息顺序异常

  • 玩家在切场景时收到旧消息

这些情况如果不提前设计,后期会很难处理。

所以网络层必须考虑容错。

1. 断线和重连

移动端项目中,断线是很常见的。

玩家切后台、网络波动、Wi-Fi 和移动网络切换,都可能导致连接断开。

网络层需要明确:

  • 什么情况下认为连接断开

  • 是否自动重连

  • 重连期间是否阻塞操作

  • 重连成功后是否需要重新拉取数据

  • 失败后如何提示玩家

  • 是否需要回到登录界面

这些逻辑不应该散落在每个业务系统里,而应该由统一的网络状态管理来处理。

2. 消息超时

有些请求发出去以后,如果长时间没有收到响应,就应该认为超时。

比如登录、支付、关键操作等。

消息超时需要处理得很谨慎。

不能所有消息都简单等待,也不能所有消息都无限等待。

不同类型的消息可以有不同策略。

3. 错误码处理

服务器返回错误码时,客户端不能到处写重复判断。

比较好的方式是统一处理通用错误,再把特殊错误交给业务模块。

例如:

  • 登录失效

  • 资源不足

  • 道具不存在

  • 操作太频繁

  • 服务器维护

  • 版本不一致

这些错误需要有统一入口,否则项目越大,错误处理代码越混乱。

六、为什么网络层要和框架其他模块配合

网络层不是孤立存在的。

它会和很多系统发生关系。

例如:

1. 和事件系统配合

收到服务器消息后,可能需要通知多个模块。

比如角色数据更新后:

  • UI 要刷新

  • 红点要刷新

  • 任务系统要刷新

  • 新手引导要检查

  • 本地缓存要更新

如果网络层直接调用所有模块,会导致耦合严重。

更好的方式是通过事件系统或命令系统进行分发。

这样网络层只负责接收消息,具体逻辑由对应系统处理。

2. 和 UI 系统配合

很多网络消息最终会影响 UI。

例如登录结果、背包数据、商城数据、任务状态等。

但网络层不应该直接操作 UI。

否则网络模块会和界面强耦合。

更合理的方式是:

  • 网络层接收消息

  • 数据系统更新数据

  • 事件系统通知数据变化

  • UI 根据当前状态刷新

这样 UI 和网络之间不会直接绑定。

3. 和资源系统配合

有些服务器消息中会带资源 ID 或资源路径。

例如角色头像、道具图标、活动图片等。

客户端收到消息后,可能需要加载对应资源。

这时网络层不应该直接加载资源,而应该把数据交给业务模块,由业务模块通过资源系统处理。

这样职责更清晰。

4. 和配置表系统配合

服务器消息中经常会包含配置 ID。

例如 itemID、skillID、taskID。

客户端收到 ID 后,需要查配置表得到名称、图标、描述、数值等。

这就需要网络层和配置表系统配合。

但配合方式不是网络层直接依赖所有配置表,而是业务模块根据消息内容去查询对应配置。

七、为什么需要多种通信方式

不同项目、不同平台、不同业务场景,对网络通信方式的需求不一样。

MyFramework 中网络模块会考虑多种通信方式,例如:

  • TCP

  • UDP

  • WebSocket

  • HTTP

它们适合的场景不同。

1. TCP

TCP 适合大多数长连接游戏逻辑。

比如登录后保持连接,持续收发游戏消息。

它的优势是可靠、有序,适合大部分业务消息。

2. UDP

UDP 适合对实时性要求更高、允许一定丢包的场景。

例如某些实时同步、位置同步、状态广播等。

当然 UDP 的可靠性需要业务层或网络层额外设计。

3. WebSocket

WebSocket 对 WebGL 或某些跨平台场景比较重要。

如果游戏需要运行在 Web 平台,WebSocket 通常会成为重要选择。

4. HTTP

HTTP 更适合一些短请求。

例如公告、版本检查、资源地址获取、某些平台接口等。

所以网络框架不应该只绑定一种通信方式,而应该根据项目需求支持扩展。

八、网络层的可调试性非常重要

网络问题最怕不好查。

很多 Bug 表面看是业务逻辑问题,实际可能是网络消息导致的。

比如:

  • 某个 UI 没刷新

  • 某个道具数量不对

  • 某个任务状态异常

  • 某个角色位置不正确

  • 某个操作没有响应

这些问题都可能和网络消息有关。

所以网络层需要尽量方便调试。

我认为至少要能做到:

  • 查看发出了哪些消息

  • 查看收到了哪些消息

  • 查看消息 ID

  • 查看消息字段

  • 查看消息耗时

  • 查看连接状态

  • 查看错误码

  • 查看断线和重连日志

如果网络层没有足够日志,很多问题会非常难排查。

但日志也不能无脑打印。

因为网络消息可能非常频繁。

所以需要根据开发环境、调试开关、消息类型来控制日志输出。

九、网络层和服务器框架的关系

MyFramework 有配套服务器框架 MyServerFramework

这使得网络通信设计不只是客户端单方面的问题。

客户端和服务器应该尽量在工具链上保持一致。

例如:

  • 使用同一份协议定义

  • 生成客户端消息代码

  • 生成服务器消息代码

  • 两端使用相同消息 ID

  • 两端使用一致的字段顺序

  • 两端都能根据协议变化重新生成代码

这样可以减少大量人为同步错误。

对于只有客户端框架的项目来说,服务器协议可能是外部系统。

但对于自己同时维护客户端和服务器的项目来说,协议工具链非常重要。

我更希望客户端和服务器之间不是靠口头约定,也不是靠复制粘贴代码,而是通过工具生成来保持一致。

这也是 MyFrameworkMyServerFramework 配套设计的一个重点。

十、网络层不是越复杂越好

虽然网络层很重要,但它也不是越复杂越好。

一个好的网络层应该做到:

  • 底层通信清晰

  • 消息协议统一

  • 代码生成稳定

  • 收发流程明确

  • 错误处理集中

  • 日志方便调试

  • 业务层使用简单

  • 扩展时不破坏旧结构

很多时候,网络层最怕的是职责不清。

如果网络层既负责连接,又负责业务逻辑,又负责 UI,又负责资源加载,最后一定会变得很难维护。

所以网络层应该有明确边界。

它应该负责:

  • 连接

  • 发送

  • 接收

  • 序列化

  • 反序列化

  • 消息注册

  • 消息派发

  • 网络状态

而具体业务逻辑应该交给业务系统处理。

十一、MyFramework 中网络模块的目标

MyFramework 中,我对网络模块的目标可以总结为几个点。

1. 协议统一

客户端和服务器基于同一份协议定义生成代码,减少两端不一致的问题。

2. 收发清晰

消息从创建、发送、接收、解析到派发,流程尽量清晰可追踪。

3. 容错可控

断线、重连、错误码、超时等问题尽量有统一处理方式。

4. 便于调试

网络日志、消息 ID、消息字段、连接状态等信息应该方便查看。

5. 适合长期维护

网络层不只是能跑,而是要能随着项目长期演进。

新增协议、修改字段、增加模块时,不能让维护成本持续上升。

结语

网络通信是游戏项目中非常核心的一层。

它看起来只是客户端和服务器之间的数据传输,但实际上会影响整个项目的稳定性、可维护性和扩展能力。

我在 MyFramework 中设计网络模块时,关注的不是简单封装 Socket,而是希望构建一套更完整的通信流程:

协议统一。

代码生成。

消息收发清晰。

客户端和服务器配套。

异常情况可控。

长期维护成本可接受。

对于长期 Unity 商业项目来说,网络层不是可有可无的封装,而是整个工程体系中的基础设施。

如果你正在做 RPG、MMO、SLG、模拟经营,或者任何需要长期维护客户端和服务器通信的项目,那么网络通信层都值得认真设计。

项目地址:

https://github.com/ZHOURUIH/MyFramework

配套服务器框架:

https://github.com/ZHOURUIH/MyServerFramework

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐