Fork me on GitHub

网游中的网络编程系列1:UDP vs. TCP

原文:UDP vs. TCP,作者是Glenn Fiedler,专注于游戏网络编程相关工作多年。

目录

  1. 网游中的网络编程系列1:UDP vs. TCP
  2. 网游中的网络编程2:发送和接收数据包
  3. 网游中的网络编程3:在UDP上建立虚拟连接
  4. TODO

说在最前面的话

翻译这篇文章的初衷:我在工作中根本接触不到网络游戏编程,但是我不想把自己定义为‘网站开发工程师’,正像我师父告诉我的:“别说开发网站,太low!要说开发web应用”。那么,网络游戏开发web方面的知识真的应该了解下。锻炼自己英文的同时,可以分享些东西给“小伙们”,哇咔咔~。

这是一个系列的文章,前面的东西一定要弄懂,后面就会轻松的搞明白(我后面的还没看,但是要先给自己打打气😋)。

注:括号中的内容都是我加上去的,为了有助于理解。

介绍

你一定听说过sokcet(初探socket),它分为两种常用类型:TCP和UDP。当要写一个网络游戏,我们首先要选择使用哪种类型的socket。是用TCP、UDP还是两者都用?

选择哪种类型,完全取决于你要写的游戏的类型。后面的文章,我都将假设你要写一个‘动作’网游。就像:光环系列,战地1942,雷神之锤,这些游戏。

我们将非常仔细的分析这两种socket类型的优劣,并且深入到底层,弄清楚互联网是如何工作的什么。当我们弄清楚这些信息后,就很容易做出正确的选择了。

TCP/IP

TCP代表“传输控制协议”,IP代表:“互联网协议”,你在互联网上做任何事情,都是建立在这两者的基础上,比如:浏览网页、收发邮件等等。

TCP

如果你曾经用过TCP socket,你肯定知道它是可靠连接的协议,面向连接的传输协议。简单的说:两台机器先建立起连接,然后两台机器相互发送数据,就像你在一台计算机上写文件,在另外一个台读文件一样。(我是这么理解的:TCP socket就像建立起连接的计算机,之间共享的一个'文件'对象,两者通过读写这个'文件'实现数据的传输)

这个连接是可靠的、有序的,代表着:发送的所有的数据,保证到达传输的另一端的时候。另一端得到的数据,和发送数据一摸一样(可靠,有序。例如:A发送数据‘abc’,通过TCP socket传输数据到B,B得到数据一定是:‘abc’。而不是‘bca’或者‘xueweihan’之类的鬼!😋)。传输的数据是‘数据流’的形式(数据流:用于操作数据集合的最小的有序单位,与操作本地文件中的stream一样。所以TCP socket和文件对象很像),也就是说:TCP把你的数据拆分后,包装成数据包,然后通过网络发送出去。

注意:就像读写文件那样,这样比较好理解。

IP

“IP”协议是在TCP协议的下面(这个牵扯到七层互联网协议栈,我就简单的贴个图不做详细的介绍)

“IP”协议是没有连接的概念,它做的只是把上一层(传输层)的数据包从一个计算传递到下一个计算机。你可以理解成:这个过程就像一堆人手递手传递纸条一样,传递了很多次,最终到达纸条上标记的xxx手里(纸条上写着‘xxx亲启,偷看者3cm’😄)。

在传递的过程中,不保证这个纸条(信件)能能够准确的送到收信人的手上。发信人发送信件,但是永远不知道信件是否可以准确到达收件人的手上,除非收件人回信告诉他(发信人):“兄弟我收到信了!”(IP层只是用于传递信息,并不做信息的校验等其它操作)

当然,传递信息的这个过程还是还是很复杂的。因为,不知道具体的传递次序,也就是说,因为不知道最优的传递路线(能够让数据包快速的到达目的地的最优路径)所以,有些时候“IP”协议就传递多份一样的数据,这些数据通过不同的路线到达目的地,从而发现最优的传递路线。

这就是互联网设计中的:自动优化和自动修复,解决了连接的问题。这真的是一个很酷的设计,如果你想知道更多的底层实现,可以阅读关于TCP/IP的书。(推荐上野宣的图解系列)

UDP

如果我们想要直接发送和接受数据包,那么就要使用另一种socket。

我们叫它UDP。UDP代表“用户数据包协议”,它是另外一种建立在IP协议之上的协议,就像TCP一样,但是没有TCP那么多功能(例如:建立连接,信息的校验,数据流的拆分合并等)

使用UDP我们能够向目标IP和端口(例如80),发送数据包。数据包会达到目标计算机或者丢失。

收件人(目标计算机),我们只需要监听具体的端口(例如:80),当从任意一台计算机(注意:UDP是不建立连接的)接受到数据包后,我们会得知发送数据包的计算机地址(IP地址)和端口、数据包的大小、内容。

UDP是不可靠协议。现实使用的过程中,发送的大多数的数据包都会被接收到,但是通常会丢失1-5%,偶尔,有的时候还可能啥都接收不到(数据包全部丢失一个都没接收到,传递数据的计算机之间的计算机的数量越多,出错的概率越大)。

UDP协议中的数据包也是没有顺序的。比如:你发送5个包,顺序是1,2,3,4,5。但是,即接收到的顺序可能是3,1,4,2,5。现实使用的过程中,大多时候,接收到的数据的顺序是正确的,但是并不是每次都是这样。

最后,尽管UDP并没有比“IP”协议高级多少,而且不可靠。但是你发送的数据,要么全部到达,要么全部丢失。比如:你发送一个大小为256 byte的数据包给另外一台计算机,这台计算机不会只接收到100 byte的数据包,它只可能接收到256 byte的数据包,或者什么都没接收到。这是UDP唯一可以保证的事情,其它所有的事情都需要你来决定(我的理解,UDP协议只是个简单的传输协议,只保证数据包的完整性,注意是数据包而不是信息。其他的事情需要自己去做,完善这个协议,达到自己使用的需求。)

TCP vs. UDP

我们如何选择是使用TCP socket还是UDP socket呢?

我们先看看两者的特征吧:

TCP:

  • 面向连接

  • 可靠、有序

  • 自动把数据拆分成数据包

  • 确保数据的发送一直在控制中(流量控制)

  • 使用简单,就像读写文件一样

UDP:

  • 没有连接的概念,你需要自己通过代码实现(这个我也没自己实现过,应该还会讲)

  • 不可靠,数据包无序,数据包可能无序,重复,或者丢失

  • 你需要手动地把数据拆分成数据包,然后发送数据包

  • 你需要自己做流量控制

  • 如果数据包太多,你需要设计重发和统计机制

通过上面的描述,不难发现:TCP做了所有我们想做的事情,而且使用十分简单。反观UDP就十分难用了,我们需要自己编写设计一切。很显然,我们只要用TCP就好了!

不,你想的简单了(原来,是我太年轻了!)

当你开发一个像上面说过的FPS(动作网游)的时候使用TCP协议,会是一个错误的决定,这个TCP协议就不好用了!为什么这么说?那么你就需要知道TCP到底做了什么,使得一起看起来十分简单。(让我们继续往下看,这是我最好奇的地方!!!有没有兴奋起来?🤓)

TCP内部的工作原理

TCP和UDP都是建立在“IP”协议上的,但是它俩完全不同。UDP和“IP”协议很像,然而TCP隐藏了数据包的所有的复杂和不可靠的部分,抽象成了类似文件的对象。

那么TCP是如何做到这一点呢?

首先,TCP是一个数据流的协议,所以你只需要把输入的内容变成数据流,然后TCP协议就会确保数据会到达发送的目的地。因为“IP”协议是通过数据包传递信息,TCP是建立在“IP”协议之上,所以TCP必须把用户输入的数据流分成数据包的形式。TCP协议会对需要发送的数据进行排队,然后当有足够的排除数据的时候,就发送数据包到目标计算机。

当在多人在线的网络游戏中发送非常小的数据包的时候,这样做就有一个问题。这个时候会发生什么?如果数据没有达到缓冲区设定的数值,数据包是不会发送的。这就会出现个问题:因为客户端的用户输入请求后,需要尽快的从服务器得到响应,如果像上面TCP 等待缓冲区满后才发送的话,就会出现延时,那么客户端的用户体验就会非常差!网络游戏几乎不能出现延时,我们希望看到的是“实时”和流畅。

TCP有一个选项可以修复,上面说的那种等待缓冲区满才发送的情况,就是TCP_NODELAY。 这个选项使得TCP socket不需要等待缓冲区满才发送,而是输入数据后就立即发送。

然而,即使你已经设置了TCP_NODELAY选项,在多人网游中还是会有一系列的问题。

这一切的源头都由于TCP处理丢包和乱序包的方式。使得你产生有序和可靠的“错觉”。

TCP如何保证数据的可靠性

本质上TCP做的事情,分解数据流,成为数据包,使用在不可靠的“IP”协议,发送这些数据包。然后使得数据包到达目标计算机,然后重组成数据流。

但是,如何处理当丢包?如何处理重复的数据包和乱序数据包?

这里不会介绍TCP处理这些事情的细节,因为这些都是非常复杂的(想弄清楚的同学可以看我上面推荐的书单),大体上:TCP发送一个数据包,等待一段时间,直到检测到数据包丢失了,因为没有接收到它的ACK(一种传输类控制符号,用于确认接收无误),接下来就重新发送丢失的数据包到目标计算机。重复的数据包将被丢弃在接收端,乱序的数据包将被重新排序。所以保证了数据包的可靠性和有序性。

如果我们用TCP实现数据的实时传输,就会出现一个问题:TCP无论什么情况,只要数据包出错,就必须等待数据包的重发。也就是说,即使最新的数据已经到达,但还是不能访问这些数据包,新到的数据会被放在一个队列中,需要等待丢失的包重新发过来之后,所有数据没有丢失才可以访问。需要等待多长时间才能重新发送数据包?举个例子:如果的延时是125ms,那么需要最好的情况下重发数据包需要250ms,但是如果遇到糟糕的情况,将会等待500ms以上,比如:网络堵塞等情况。那就没救了。。。

为什么TCP不应该用于对网络延时要求极高的条件下

如果FPS(第一人称射击)这类的网络游戏使用TCP就出现问题,但是web浏览器、邮箱、大多数应用就没问题,因为多人网络游戏有实时性的要求。比如:玩家输入角色的位置,重要的不是前一秒发生了什么,而是最新的情况!TCP并没有考虑这类需求,它并不是为这种需求而设计的。

这里举一个简单的多人网游的例子,比如射击的游戏。对网络的要求很简单。玩家通过客户端发送给服务器的每个场景(用鼠标和键盘输入的行走的位置),服务器处理每个用户发送过来的所有场景,处理完再返回给客户端,客户端解析响应,渲染最新的场景展示给玩家。

在上面说的哪个多人游戏的例子中,如果出现一个数据包丢失,所有事情都需要停下来等待这个数据包重发。客户端会出现等待接收数据,所以玩家操作的任务就会出现站着不动的情况(卡!卡!卡!😡💢),不能射击也不能移动。当重发的数据包到达后,你接收到这个过时的数据包,然而玩家并不关心过期的数据(激战中,卡了1秒,等能动了,都已经死了💀)

不幸的是,没有办法修复TCP的这个问题,这是它本质的东西,没办法修复。这就是TCP如何做到让不可靠,无序的数据包,看起来像有序,可靠的数据流。

我并不需要可靠,有序的数据流,我们希望的是客户端和服务端之间的延时越低越好,不需要等待重发丢失的包。

所以,这就是为什么在对数据的实时性要求的下,我们不用TCP。

那为什么不UDP和TCP一起用呢?

像玩家输入实时游戏数据和状态的变更,只和最新的数据有关(这些数据强调实时性)。但是另外的一些数据,例如,从一台计算机发送给另外一个台计算机的一些列指令(交易请求,聊天?),可靠、有序的传输还是非常重要的!

那么,用户输入和状态用UDP,TCP用于可靠、有序的数据传输,看起来是个不错的点子。但是,问题在于TCP和UDP都是建立“IP”协议之上,所以协议之间都是发送数据包,从而相互通信。协议之间的互相影响是相当复杂的,涉及到TCP性能、可靠性和流量控制。简而言之,TCP会导致UDP丢包,请参考这篇论文

此外,UDP和TCP混合使用是非常复杂的,而且实现起来是非常痛苦的。(这段我就不翻译了,总而言之:不要混用UDP和TCP,容易失去对传输数据的控制)

总结

我的建议并不是就一定要使用UDP,但是UDP协议应该用于游戏。请不要混合使用TCP和UDP,你应该学习TCP中一些地方是如何实现的技巧,然后可以把这些技巧用在UDP上,从而实现适合你的需求的协议(借鉴TCP中的实现,在UDP上,完善功能,从而达到你的需求)。

这个系列,接下来会讲到:如何在UDP上创建一个虚拟的连接(因为UDP本身,是没有连接的概念的)、如何使得UDP实现可靠性,流量控制,非阻塞。

参考

posted @ 2016-05-02 19:10 削微寒 阅读(...) 评论(...) 编辑 收藏