news 2026/8/17 18:14:56

Unity 网络通信极限选型:TCP 和 UDP 到底该怎么分工,选错一次可能就是性能灾难?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity 网络通信极限选型:TCP 和 UDP 到底该怎么分工,选错一次可能就是性能灾难?

TCP 可靠、UDP 快,所以“重要数据走 TCP,位置同步走 UDP”——这句话没错,但只说到了表面。

游戏网络真正要判断的并不是“哪个协议更快”,而是:

这条消息如果晚到、丢失、重复,甚至被下一条消息覆盖,到底有没有关系?

MyFramework 同时维护 TCP 和 UDP 两条通信链路,业务层仍然只调用统一的sendPacket()。这一篇就结合实际代码看看,两种协议到底应该怎么分工。

项目地址:

GitHub - ZHOURUIH/MyFramework: Unity 商用级别开发框架,经过了多年经验沉淀.一个在unity上使用的网络游戏客户端开发框架,为unity所有使用方式提供完善的封装和管理,只需要专注于游戏逻辑的编写 · GitHub

一、业务层根本不直接选择 TCP 或 UDP

发送一条协议时,业务代码通常只有:

CSChangePosition packet = CSChangePosition.get(); ... sendPacket(packet);

真正选择传输通道的是NetManager

public void sendPacket(NetPacket packet) { if (mNetPacketTypeManager.isUDPPacket(packet.getPacketType())) { Character myself = mCharacterManager.getMyself(); if (myself != null) { mUDPServer.setToken(myself.getGUID()); } mUDPServer.sendNetPacket(packet); } else { mTCPServer.sendNetPacket(packet); } }

所以整个业务层仍然面对同一种:

NetPacket

只是协议注册阶段决定:

这个Packet ↓ TCP 还是 UDP

这样业务代码不需要到处写:

if (useUDP) { ... } else { ... }

传输方式成为了协议本身的属性。

二、选 TCP 还是 UDP,先问一个问题

假设角色连续产生三次位置:

A:100,100 B:105,100 C:110,100

如果 A 丢了,但 C 已经到了:

A × B √ C √

还需要想办法把 A 找回来吗?

显然没必要。

因为:

最新位置 C 已经让旧位置 A 失去价值

这就是 UDP 最适合的一类数据:

旧数据会被新数据自然覆盖。

但换成:

购买物品 扣除金币 领取奖励 交易确认

情况完全不同。

“购买成功”这个消息不能因为下一条消息到了,就认为上一条可以不要。

所以第一条判断原则其实是:

新消息能不能代替旧消息?

能,才有资格考虑 UDP。

不能,优先 TCP。

三、为什么位置同步特别适合 UDP

项目中的CSChangePosition就是一条典型的位置同步协议:

public BIT_VECTOR2_INT mPixelPos = new(); public BIT_LONG mTimeStamp = new(); public BIT_USHORT mNormalMoveSpeed = new(); public BIT_USHORT mMoveTimeMS = new(); public BIT_USHORT mMoveSpeed = new(); public BIT_USHORT mMoveDelta = new(); public BIT_USHORT mMoveDeltaAdjusted = new(); public BIT_USHORT mMapID = new(); public BIT_BOOL mRun = new();

这里甚至包含:

位置 时间戳 移动速度 移动时间 地图ID 移动状态

也就是说它表达的是:

角色此时此刻是什么状态。

如果 100ms 前的位置包丢了,而新的位置已经到达,通常继续使用最新状态即可。

而且CSChangePosition自己还携带:

mTimeStamp

这类时间信息也给业务层判断数据新旧留下了空间。

UDP 不可靠,并不意味着协议设计就必须完全不知道“新旧”。

四、真正麻烦的是:TCP 和 UDP 之间根本没有顺序

这一点比“UDP 会丢包”更容易被忽略。

假设服务器依次产生:

TCP:玩家离开场景 UDP:玩家位置同步

或者位置包其实更早发出,只是在网络上晚到了。

客户端完全可能先收到:

玩家离开场景

然后又收到一个旧的:

玩家位置同步

因为:

TCP 只能保证 TCP 自己内部有序。UDP 和 TCP 两条链路之间,没有一个共同的消息顺序。

项目里的SCOtherPlayerPosition就专门处理了这个问题:

CharacterPlayer player = mOtherPlayerManager.getPlayerOther(mPlayerGUID[i]); if (player == null) { return; } // 位置同步使用UDP,其他消息使用TCP, // 所以玩家离开场景后仍可能收到位置消息 if (!mMapSceneManager.getMap().isPlayerInScene(player)) { return; }

这段代码非常有代表性。

TCP 已经告诉客户端:

这个玩家离开场景了

但 UDP 的旧位置包可能随后才飞过来。

所以不能看到位置包就无脑应用,而是必须再次确认:

这个玩家现在还在不在场景?

这其实就是混合 TCP + UDP 后必须面对的跨通道时序问题

五、UDP 不只是“更快”,而是不会等待旧数据

TCP 还有一个非常关键的特性。

假设:

Packet A Packet B Packet C

底层属于同一条 TCP 字节流。

如果 A 对应的一部分数据丢失,即使 B、C 的网络数据已经到达,应用层也不能越过 A 直接拿到后面的字节。

TCP 必须先把缺失数据恢复出来。

对于:

交易 背包 任务 登录

这是我们想要的。

但对于高频位置:

A:1秒前的位置 B:500ms前的位置 C:现在的位置

如果 A 已经过时,却还阻塞着后面的 C,就没有那么划算了。

UDP 的思路完全不同:

A 丢了 ↓ 算了 B 到了 ↓ 处理 C 到了 ↓ 继续处理

它不保证可靠,却避免了旧数据拖住新数据。

所以实时同步真正看重的不只是:

延迟低

而是:

最新状态能够尽快到达业务层。

六、哪些消息绝对不能直接扔给 UDP

例如购买道具:

客户端: 购买商品1001 服务器: 扣100金币 增加1个商品

如果用当前这种没有 ACK、重传、去重机制的 UDP:

请求丢了 → 到底要不要重发? 重发了 → 第一条是不是其实已经到了? 到了两次 → 会不会购买两次?

马上就会引入:

请求ID ACK 超时 重传 去重 幂等 顺序控制

写到最后,本质上是在 UDP 上重新实现一套可靠协议。

而 TCP 已经帮你做了这些底层工作。

所以类似:

登录 购买 交易 邮件 任务提交 背包操作 装备操作 奖励领取

这种每一次状态变化都有业务意义的消息,更适合直接交给 TCP。

七、UDP 最大的问题不是丢包,而是你必须接受它丢包

MyFramework 当前NetConnectUDPBit没有实现:

ACK 丢包重传 乱序重排 重复包过滤

这其实反而让 UDP 的定位非常清晰。

它不是:

用 UDP 实现另外一套 TCP。

而是:

只把真正允许丢失的数据放进去。

所以判断一条消息是否适合 UDP,可以连续问四个问题:

丢一次能接受吗? 晚到能接受吗? 新消息能覆盖旧消息吗? 乱序到达时业务能识别或容忍吗?

只要其中某一个答案是:

不能

就应该非常谨慎。

八、不要为了省十几个字节强行用 UDP

从纯协议开销看,在最基础的 IPv4 情况下,不考虑额外选项:

TCP Header:至少20 Byte UDP Header:8 Byte

UDP 确实更轻。

但游戏里选 UDP 的核心原因绝对不是:

每个包少12 Byte

而是它的传输语义更适合:

高频 实时 允许丢失 最新状态优先

如果为了省一点包头,把交易系统改成 UDP,然后自己增加:

ACK + Sequence + Retry + Timeout + Duplicate Check

最后可能不仅没省多少,复杂度还直接爆炸。

九、MyFramework 的 TCP / UDP 分工

最终可以总结成:

TCP ──────────────── 可靠、有序 旧数据不能随便丢 适合业务状态变化 登录 背包 装备 交易 任务 邮件 购买 奖励 UDP ──────────────── 允许丢包 最新数据可以覆盖旧数据 不等待历史状态 角色位置 高频实时状态 允许丢失的同步数据 UDP心跳

而 MyFramework 在上层把两套传输统一成了:

NetPacket ↓ sendPacket() ↓ 根据PacketType判断 ↙ ↘ TCP UDP ↓ ↓ NetConnectTCP NetConnectUDP

业务层不需要关心 Socket。

真正需要想清楚的是:

这条消息到底属于“事件”,还是“状态”。

一次购买、一次交易、一次领取奖励,是不能消失的事件

角色现在在哪里、朝向哪里、正在以什么速度移动,更接近可以不断覆盖的状态

这往往比“TCP 和 UDP 谁性能更高”更能决定一条游戏协议到底应该走哪条链路。

而一旦同时使用两条链路,还必须记住最后一个坑:

TCP 内部有顺序,UDP 内部不保证顺序,而 TCP 和 UDP 之间更不存在任何全局顺序。

这也是为什么真正成熟的游戏网络代码,绝不会只是简单地把Send()换成SendTo()

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/17 18:12:35

lift-oQ4 vs lift-oQ8 vs bf16:MLX社区7大量化版本怎么选最合适

lift-oQ4 vs lift-oQ8 vs bf16:MLX社区7大量化版本怎么选最合适 【免费下载链接】lift-oQ4 项目地址: https://ai.gitcode.com/hf_mirrors/mlx-community/lift-oQ4 lift-oQ4 是 MLX 社区针对文档抽取视觉大模型 lift(9B Qwen3.5)推出…

作者头像 李华
网站建设 2026/8/17 18:10:33

Qwen3.8与TokenSpeed组合部署:大幅降低大模型服务成本

这次我们来看一个能让大模型部署成本大幅降低的技术组合:Qwen3.8 模型与 TokenSpeed 推理加速框架。对于需要将大语言模型投入实际生产,尤其是面临高并发、低延迟、低成本挑战的团队来说,这组方案值得重点关注。Qwen3.8 是通义千问团队最新开…

作者头像 李华