在编写网络游戏时,最终会出现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的东西。您还使用单个套接字进行通信–与TCP不同,TCP要求每个连接的客户端都有一个套接字。这些东西大部分都是好东西。
但是,对于大多数情况,您实际上需要一些连接概念,一些基本的顺序以及可靠性。UDP都不提供“开箱即用”功能,而使用TCP可免费获得。
人们经常推荐使用TCP。使用TCP,您可以上手,而不必为这些事情担心太多–直到您开始同时拥有500多个同时连接。
因此,是的,UDP没有提供完整的工具包,但是正如我们将要看到的,这就是为什么它如此出色的原因。从某种意义上说,TCP就是UDP,就像Hibernate一样是用SQL手动编写查询。
TCP的有缺陷的情况
人们通常会建议“ TCP与UDP一样快”或“成功的X游戏正在使用它,因此它起作用”这一想法就与TCP搭配使用,而并没有 真正理解它为何在特定游戏中起作用以及为什么UDP与正常的数据包传送速度无关。
那么为什么《魔兽世界》可以和TCP一起使用呢?首先,我们需要重新表述这个问题。问题应该是:“为什么偶尔会有1000毫秒以上的延迟,但《魔兽世界》为什么还能工作?”。因为这就是TCP的实际情况–在丢弃的数据包上,您会遇到巨大的延迟,因为TCP首先需要检测丢失的数据包,然后重新发送所有数据包,同时降低吞吐量。
可靠的UDP也会有延迟,但是由于它是您在UDP之上编写的任何协议的属性,因此有可能以多种方式减少延迟–与TCP不同,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连接到游戏服务器。
永远不要害怕使用最好的工具来完成一项任务。