`
micheal19840929
  • 浏览: 161480 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

网络游戏程序员须知 基于UDP的虚拟连接

阅读更多

本文为作者原创翻译并且加入了一些自己的思路和观点,转载请注明。

作者:rellikt@gmail.com
首发链接:http://blog.csdn.net/rellikt/archive/2010/08/28/5846647.aspx
原文链接:http://gafferongames.wordpress.com/networking-for-game-programmers/virtual-connection-over-udp/
      大家好,欢迎再次来到我的博客。
      上一篇中我给大家介绍了简单的UDP协议的发包与收包的过程。
      我们知道只要开了一个UDP的端口,我们就可以通过这个端口和任意个其他机器进行通信。但是通常在多人游戏中,我们只需要和特定的一组机器进行通信即可。
      既然这样,那么我们就从最基础的2台机器互联开始讲起吧。
      在开始之前,我还想更深入的讲点关于互联网的知识。 by rellikt
互联网并不是管道网络传输模型
      在2006年Ted Stevens议员关于因特网历史的著名演讲中有一段提到了网络的神经元性特徵。

      “网络传输并不是那种可以一下子传输一大块东西的,类似大卡车运输的传输模型,它的模型是用很多类似下水道网的管道网络进行分散的传播的模型。”

      当我首次使用互联网的时候,我和Ted是一样的无知。我小心翼翼的坐在1995年悉尼大学的实验室中,通过网景浏览器上网,那个时候我对电脑中具体发生了点什么其实是一无所知的。

      我那时候认为,我们每次连接到一个站点,我们就会建立一个相应的连线,就和打电话一样。我还在怀疑我是不是要为这个连线去付钱。是不是会突然出现一个电信收费账单,然后我就必须为我这个长途连线支付高额的通信费用呢?:)

      现在看来大家是不是觉得我那个时候有点傻呢?by rellikt

      事实上这个连接是完全没有类似电话交换机这种东西介入的,当然也没有Ted提到的什么管道网络。

没有直接连线
      网络传输的实质是通过IP协议将包从一台机器发到另外一台机器。

      包从发送端到达接收端往往需要经过很多层的机器路由。你事先无法预测你的包会被那些机器路由,因为路由规则会根据网络状况做不断的调整。就算你对同一个地址发2个相同的包,这两个包也可能走的是不同的路由。这就是我UDP协议中发包收包序列性无法保证的根本原因。

      在类Unix的系统中,我们可以同traceroute命令加上IP参数或者域名来查看具体路由。

      在windows系统中,我们用tracert也能做相同的工作。

 试着输入如下的命令:

 traceroute amazon.com

 traceroute google.com

 .......

      看到结果了吗?现在你应该可以确信,我们的网络连接的确不像电话那样有真实的连线了吧。 by rellikt

包是怎么递送的
      在第一篇中,我提到了一个在上课的教室中传纸条的网络传输模型。

      这个模型对于我们要的基本传输概念来说的确是已经足够了,但是现在对我们来说这个模型有限过于简单了。互联网其实并不是简单的一层网络结构体系,事实上它是一个由各个下级网络组成的一个网络的网络。我们要做的不仅仅是在一个小房间中传纸条,我们其实还要把纸条传出房间,传向世界。

      其实这里的模型用邮局来解释是最合适不过的了。

      如果你要给你的朋友寄信。一般来说,你只要把收件人的信息填写完整,贴上足够的邮票,然后再把信放到邮箱里面就行了。你不用担心邮件的递送流程,你只知道有人会帮你完成这个递送流程。就是这么简单。

      当然我们可以确定不会有单个的邮递员会独自去跑一趟帮你送信。邮递员做的只是把你的信带到邮局,然后从邮局把他需要送的信带出来。看起来邮件用的也不是那种Ted提到的分散递送的管道网络系统。

      邮局收到邮递员给的信以后,如果这个信是本地的,那么往往就分一下类,第二天让邮递员直接送就完事了。如果不是本地的,那么一般会把信给到更高级的邮局,比如省局,省局如果一看是不是本省的还要往上层送。如果是国际邮件的话,一般要送到国家邮局。这里省局,国家邮局间互相递送邮件的过程,往往就会用到大卡车,甚至大飞机了!

      国家邮局收到邮件以后往往会把邮件再按照省局分发,然后省局发到县局,县局在发到专门负责片区的邮递员,邮递员然后在发给工厂门卫李大爷,李大爷最后转交给收件人。就是这么简单,你的邮件完成了投递。 by rellikt

      回头再来看看我们的数据包的递送。和邮局的邮件一样我们的包会有一个接收者的IP地址。然后网络就根据这个IP地址来发送包。底层的路由算法实现是很复杂的,我们这里不做详细讨论。大概的思路是包被发送到路由器计算机,然后这台路由器会有一个路由表,路由表描述了不同的IP分组会被递送的方向,另外还有一个当包的IP地址无法通过本地路由表找到递送机器时的处理方法。就是这些路由器的实际物理链接和路由表的递送算法构成了我们的互联网。

      维护路由表一般是网络管理员的工作,和我们游戏程序员的工作没有关系。如果你真有兴趣了解的话,这篇从ars technica上的论文描述了包是如何在不同的机器间传输的。你还可以从linux faq中获得路由表中的更多细节。边际路由的知识则可以在这篇维基上的介绍中获得。这些东西真正的构成了我们现代基于无连接的分布式动态路由互联网络。

虚拟连接
      现在我们再来看一下我们这里游戏中的连接。

      如果你使用了TCP的协议,那么你似乎在不同的机器里建立了连线。但是我们知道TCP其实是基于IP协议的,而IP协议其实是在不同计算机间发包的一种无连接协议。那么我们可以得出结论,其实TCP的连接是虚拟的连接。

      这么说来,如果TCP可以基于IP协议做出一种虚拟的连接。我们用UDP也可以做出类似的虚拟连接。

      我们可以这样定义我们的自己的虚拟连接,我们通过每10秒发送一个UDP包来使两台目标机器实现通信。只要我们还在发包,那么我们就可以认为我们的虚拟连接还在。 by rellikt

      我们的连接有两个端点组成:端点一的电脑不停的在那里侦听相关端口。端点二的电脑则对端点一的电脑通过IP和端口进行连接。我们称端点一为server段,端点二为client端。

      我们先定义现在的连接模型为简单的两台电脑互联,并且这两台电脑都有公共的固定IP。关于多点互联和NAT击穿的模型拓展,我们会在后续的篇章中继续讨论。

协议ID
      我们已经在前面提到过UDP是无连接的协议,我们可以通过UDP端口对任意多的机器发包。

      由于这点,我们必须对我们创建出的UDP端口上收到的包进行筛选虑噪。简单的通过IP地址的虑噪是不可取的,因为我们并不能事先知道目标机器的IP,这里一般采用的方法是在UDP包的数据头上附加上一个32位的协议标识符,比如:

 [unit protocol id] packet data.....

      这个协议ID一般就是一个两边商定的随机数,在两边都知道情况下,作为一个标记位存在在数据包的最先的32bit中。我们收包的时候首先检测包头的32位,如果对上了暗号,我们就直接把包头去掉,然后读取包的内容。否则就把包是为噪声给无视掉。

      选择这个协议ID的时候我们只要保证一般的唯一性就可以了。记住这个ID只是用来区分我们协议包的。

检测连接
      接下来我们需要一个检测连接的方法。

      我们肯定可以学TCP一样使用三次握手的传统方式来检测连接。比如先让client端发一个包“请求连接”,然后server端回一个包“接受连接”,或者回一个包“连接忙碌,拒绝连接”什么的。 by rellikt

      但是其实我们也可以从收到第一个含有协议ID包开始,我们就直接建立连接了。既然我们已经有暗号了,何必要那多余的“喂”一下呢?
      client端就像已经建立连接一样发送带正文的数据包。然后server端在收到第一个包以后,记录下相对的client地址,然后开始回复。

      在我们的模型定义中,client是已经知道server的IP和端口的。因此它在收包的时候可以完全过滤掉非server的IP的包。server段在收到第一个包含协议ID包以后,也可以从recvfrom函数中得到client的IP。然后它会在接下来的通信中过滤掉非clent的IP的包。

      现在这个简单的协议方式对于我们的两人连接已经适用了,我们会在后面多人连接的时候对这个协议进行拓展,引入更复杂的连接建立协商机制。不过现在如果只考虑两人连接,那么这个协议完全够用了。

检测断开
      我们怎么检测断开呢?

      首先如果我们定义连接为不停的收包,那么我们这里可以把断开定义为停止收包。要检测停止收包,我们可以在机器上统计距离上次收到包的时间。两边都需要统计。我们可以做一个时间计数器的变量,每次收到包以后会清零。否则就会累加当前循环的耗时。当这个时间计数器的变量超过一定范围的时候,比如10s,我们就可以定义连接超时,然后就可以宣布断开连接了。 by rellikt

      这个方案对于解决同时有两个client要对同一个server进行连接的问题时也是很完美的。server端只会对第一个client进行回复。所以第二个client在长时间收不到包的时候会自动判定连接超时,然后断开连接。

结论
      以上这些东西组成了一个虚拟连接所需要的所有组建:建立连接,过滤噪声,断开连接。

      我们的连接现在和TCP的虚拟连接一样,但是我们的连接是由由UDP包组成的稳定的流实现的,这对于一个多人动作类游戏来说是一个很好的开端。

      在这篇中,我们也了解了一下互联网是如何路由的,并且知道了为什么我们的UDP包不会有序列性。如果你去看一下互联网的结构图,你会发现你的包能被送达简直是非常的了不起的事情。这张图是一张关于IPV6模式下分区自治管理的互联网的结构图。如果你还想深入了解,这篇wiki上的介绍会对你有帮助的。

      现在你已经可以用UDP建立一个连接了,你可以很简单的在一个多人游戏中通过非TCP的方式来建立一个client/server架构的连接。

      你可以在本文附带的代码示例中对我们上面说的理论进行测试。

      这是一个简单的c/s架构的Demo。它每秒钟发送30个包。你可以在任意的机器上对我的代码进行测试。但是你要保证自己的机器有一个公共的IP地址。现在的这个Demo并不会支持NAT击穿。 by rellikt

      client端在命令行跑的时候可以像这样带参数: /Client 205.18.15.2

      这样client端就会试图去连接你在命令行中所标明的地址的服务器端了,否则我们会默认服务器端在127.0.0.1 。

      当你的server端已经和一个client建立连接的时候,你可以尝试使用第二个client去做连接。这个Demo中这样做会失败,因为我们现在的模型还是简单的两台机器的互联模式。

      你还可以试着结束server端或者client端的程序,你会看到10s后另外一边会显示断开连接。如果另一边是server端,那么会继续做侦听,如果另一边是client端,那么会结束掉当前进程。

      看一下现阶段我提供的代码吧。接下来的东西会越来越复杂。因为我们会在下一篇中去实现能够控制流量控制序列性的可靠连接。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/rellikt/archive/2010/08/28/5846647.aspx

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics