游戏服务器:到底使用UDP还是TCP
引言
在开发网络游戏时,最终总会面临一个经典的技术选型问题:到底使用 UDP 还是 TCP?
通常,你会听到这样的建议:“除非你在做动作游戏,否则可以使用 TCP",或者“你可以将 TCP 用于你的 MMO,看看《魔兽世界》(WoW)——它就使用 TCP!”
不幸的是,这些观点往往不能正确反映 TCP/UDP 选型问题的复杂性。
背景
首先说明我的背景:我主要从事 TCP 编程工作。我曾在领先的扑克网络的游戏服务器上工作多年,通常在高峰期,每个服务器实例上运行 4,000 – 10,000 个连接(多台实例运行在同一计算机上)时没有任何问题。在我看来,TCP 是一种安全且众所周知的替代方案。
尽管如此,我们当前的项目正在使用 UDP,因为我们无法使 TCP 达到预期的连接质量。实际上,项目始于 TCP,但当很明显无法获得想要的连接质量时,我们切换到了 UDP。
TCP 在实际应用中的表现
从理论上讲,TCP 的优点如下:
- 连接建立简单且持久
- 可靠的消息传递
- 支持任意大小的数据包
任何具有 TCP 实际操作经验的人都知道,可靠的实现需要处理许多不太明显的极端情况,例如断开连接检测、由于客户端响应缓慢而导致的数据包拥塞、与建立连接有关的各种 DoS 攻击向量、阻塞与非阻塞 IO 等。
尽管具有前期的易用性,但编写一个好的 TCP 解决方案并不容易。
然而,TCP 最令人头疼的特性是拥塞控制。基本上,TCP 会将由于带宽有限而导致的数据包丢失解释为网络拥塞,并限制数据包的发送速率。
在丢失数据包的 3G/WiFi 环境下,你希望尽快发送替换数据包,但是 TCP 拥塞控制机制实际上是相反的!
没有办法完全解决这个问题,这只是 TCP 在非常基础的层面上工作的方式。由于丢失单个数据包,这可以将 3G 或 WiFi 上的 Ping 值推至 1000+ ms 范围。
为什么 UDP“更难”
UDP 比 TCP 既更容易,也更困难。
例如,UDP 是基于数据包的,这实际上是你必须自己准备使用 TCP 的东西。你还使用单个套接字(Socket)进行通信——与 TCP 不同,TCP 要求每个连接的客户端都有一个套接字。这些东西大部分都是好事。
但是,对于大多数情况,你实际上需要一些连接概念、一些基本的顺序以及可靠性。UDP 都不提供“开箱即用”功能,而这些使用 TCP 可免费获得。
人们经常推荐使用 TCP。使用 TCP,你可以快速上手,而不必为这些事情担心太多——直到你开始同时拥有 500 多个并发连接。
因此,是的,UDP 没有提供完整的工具包,但正如我们将要看到的,这就是它如此出色的原因。从某种意义上说,使用 TCP 就像使用 Hibernate ORM,而使用 UDP 则更像手动编写 SQL 查询——前者封装了细节,后者提供了更底层的控制。
TCP 适用场景的误区
人们通常会建议"TCP 与 UDP 一样快”或“成功的 X 游戏正在使用它,因此它起作用”,这一想法就与 TCP 搭配使用,而并没有 真正 理解它为何在特定游戏中起作用,以及为什么 UDP 与正常的数据包传送速度无关。
那么为什么《魔兽世界》可以和 TCP 一起使用呢?首先,我们需要重新表述这个问题。问题应该是:“为什么偶尔会有 1000 毫秒以上的延迟,但《魔兽世界》为什么还能工作?”。因为这就是 TCP 的实际情况——在丢弃的数据包上,你会遇到巨大的延迟,因为 TCP 首先需要检测丢失的数据包,然后重新发送所有数据包,同时降低吞吐量。
可靠的 UDP 也会有延迟,但是由于它是你在 UDP 之上编写的任何协议的属性,因此有可能以多种方式减少延迟——与 TCP 不同,TCP 的机制已被嵌入到协议中并且不能更改。
[这时,有些人会开始谈论 Nagle 算法,这几乎是在任何对延迟很重要的 TCP 实现中都禁用的第一件事。]
那么,为什么《魔兽世界》(和其他游戏)在这些延迟下起作用?
这仅仅是因为他们能够隐藏延迟。
就《魔兽世界》而言,没有人与人之间的碰撞检测:这种碰撞无法可靠地预测,但人与环境之间可以碰撞,因此后者与 TCP 兼容。
在《魔兽世界》中的战斗中,很容易意识到发送到服务器的命令确实是类似的东西 `attack_entity(entity_id)` 或者 `cast_spell(entity_id, spell_id)` 。换句话说,目标是与位置无关的。此外,如果服务器响应与客户端预测不同,则可以通过显示“模糊”效果来允许开始攻击动作或咒语效果之类的事情,而无需首先从服务器获得确认。
在确认之前开始操作是一种典型的延迟隐藏技术。
几年前,我为一款名为 五张爵士 的纸牌游戏编写了客户端。它基于 HTTP——在延迟方面要比普通的持久 TCP 连接差很多。
我们使用了简单的卡片绘制和向上翻转动画来隐藏延迟,因此只有在连接非常差的情况下才会出现延迟。该方法很典型:发送请求并从平台启动动画绘图卡,但是等待最后一次翻转以显示卡,直到服务器响应到达。《魔兽世界》的战斗效果以类似的方式工作。
这意味着 TCP 与 UDP 的选择基本上应该是:“我们可以隐藏延迟吗?”
当 TCP 无法胜任时
运行 TCP 的游戏要么需要能够很好地应对偶尔的延迟(通常,扑克客户端会做到这一点——偶尔的一秒钟的延迟不会让人烦恼),要么具有良好的延迟缓解技术。
但是,如果您在运行无法真正应用任何延迟缓解措施的游戏,该怎么办?玩家对玩家动作游戏通常属于此类,但不限于动作游戏。
一个例子:
我目前正在开发多人游戏 War Arcana。
在典型的游戏中,您可以将角色快速移动到最初覆盖有战争迷雾的世界地图上,但随着您的探索逐渐显示出来。
由于某些游戏规则并为了防止作弊,服务器只能显示有关角色周围环境的信息。这意味着与《魔兽世界》不同,在服务器响应到达之前,不可能完全完成移动。与“五张爵士”的唱片展示相比,这是一个难题,那就是我们可以允许最大 500 毫秒的延迟,直到动作缓慢为止。
在进行原型制作时,只要所有内容都在同一 LAN 上,一切都可以正常工作,但是一旦我们进入 WiFi,动作就会随机地停滞和滞后。编写一些测试程序表明,WiFi 偶尔会丢包,并且每次发生时,服务器响应时间将从 100-150 毫秒激增至 1000-2000 毫秒。
TCP 设置的任何调整都无法解决此问题。
我们用自定义的可靠 UDP 实现替换了 TCP 代码,从而将丢失数据包的损失降低了 50 ms(!),这比完整往返的时间还少。而且只有在 UDP 之上完全控制可靠性层时,这才有可能。
误解:可靠的 UDP 并非 TCP 的复刻
您是否曾听过这样的话:“可靠的 UDP 就像 TCP,所以请使用 TCP"?
这里的问题是该语句是错误的。可靠的 UDP 不太可能实现 TCP 的特定品牌的拥塞控制。实际上,这恰恰是您使用可靠的 UDP 而不是 TCP 摆脱其拥塞控制的最大原因。
另一个重要点是“可靠 UDP"的“可靠”部分如何工作。有许多可能的变体。我真的很喜欢 Quake 3 网络代码 的许多想法,这些想法启发了 War Arcana UDP 协议。
您也可以使用许多支持可靠 UDP 的 UDP 库之一,尽管可靠性层可能更通用,因此比手动实现的优化程度稍差。
总结与建议
那么是 UDP 还是 TCP?
- 如果您要进行偶尔的、客户端启动的无状态查询,并且可以 偶尔接受延迟,请使用基于 TCP 的 HTTP/HTTPS。
- 如果客户端和服务器都独立发送数据包,但 偶尔会有延迟(例如,在线扑克,许多 MMO),请使用持久性普通 TCP 套接字。
- 如果客户端和服务器都可以独立发送数据包并且 需要控制延迟(例如,大多数多人动作游戏,某些 MMO),请使用 UDP。
它们也可以混合使用:您的 MMO 客户端可能首先使用 HTTP 获取最新更新,然后使用 UDP 连接到游戏服务器。
永远不要害怕使用最好的工具来完成一项任务。
说明:本文提及的部分游戏案例(如 War Arcana、五张爵士)及相关链接可能随时间推移发生变化或失效,但文中关于 TCP 与 UDP 在网络游戏中的技术选型逻辑依然具有参考价值。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/you-xi-fu-wu-qi--dao-di-shi-yong-udp-hai-shi-tcp.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。