ESFramework 开发手册(04)--可靠的P2P

本文介绍ESFramework 开发手册(00) -- 概述一文中提到的四大武器中的最后一个:P2P通道。

ESPlus 2.0版本相对于1.x而言,新增的最主要特性就是对P2P的支持。

ESPlus 2.0提供了基于TCP和UDP的P2P通道,而无论我们是使用基于TCP的P2P通道,还是使用基于UDP的P2P通道,ESPlus保证所有的P2P通信都是可靠的。这是因为ESPlus在原始UDP的基础上模拟TCP的机制进行了再次封装,以使UDP像TCP一样可靠。在客户端之间需要高频通信的分布式系统中(如IM系统等),可靠的P2P通信将为您节省巨大的带宽和服务器成本。

1.P2P打洞

  了解P2P的朋友都知道,P2P Channel的建立需要通过“打洞”来完成,而运行于两个NAT设备后面的PC上的客户端实例之间的P2P打洞能否成功,或者说,P2P通道能否成功建立,取决于NAT设备的类型。

UDP打洞

  就目前我们常用的路由器、防火墙等NAT设备来说,大多都是Cone型(Full Cone、Restricted Cone、Port Restricted Cone 之一)的,所以UDP打洞的成功率还是非常大的(如80%以上)。
  基于UDP的P2P打洞成功率与NAT设备的类型的关系一览表如下所示:
ESFramework 开发手册(04)--可靠的P2P_第1张图片

  从列表可以看出,最麻烦的是Symmetric类型,如果需要P2P通信的双方有一方是Symmetric,那么打洞就需要用到端口预测技术,而且其打洞成功希望非常渺茫。
  大家可以通过从网上下载NAT设备类型的检测程序(比如STUN)来检测自己路由器等NAT设备的类型,以此来确定通信的双方基于UDP的P2P通道是否可以创建成功。

TCP打洞

TCP打洞的原理几乎与UDP是一样的,但不幸的是,目前支持TCP打洞的NAT设备非常少,以至于位于两个NAT后面的客户端实例之间能成功建立基于TCP的P2P连接的机会就很小了。我们希望在不久的将来,支持TCP打洞的NAT设备会逐渐多起来,这需要时间。关于基于TCP的P2P的更多介绍可以参见这里
  即使如此,ESFramework支持基于TCP的P2P还是非常必要的,因为在以下两种情况下,基于TCP的P2P通道肯定是可以成功创建的。

  • 通信的双方位于同一个局网内。
  • 通信的双方中至少有一方运行于具有公网IP的机器上。

在这两种情况下,都不需要TCP打洞,也不需要NAT设备的额外支持,基于TCP的P2P通道就可以成功建立。

2.P2P通道的可靠性

   由于我们的P2P通道可能是基于TCP的、也可能是基于UDP的,所以P2P通道就继承了协议的特性:基于TCP的P2P通道是可靠的、而基于UDP的P2P通道是不可靠的。

值得庆幸的是,ESFramework/ESPlus内部使用了增强的UDP -- 在UDP的基础上模拟TCP机制,以保证通信的可靠性。所以,ESPlus提供的P2P通道都是可靠的。

同时,ESPlus也提供了选择,可以使用原始的UDP,只要设置IRapidPassiveEngine的ReliableUdpEnabled属性为false即可。如此,ESPlus基于UDP创建的P2P通道就是不可靠的。

3.通道选择

在介绍CustomizeInfo空间时,我们提到可以使用ICustomizeOutter发送一个P2P消息给另外一个在线的用户,该接口的如下几个方法发送的都是P2P消息:

除SendByP2PChannel方法外,其它的方法都将使用可靠的通道来发送P2P消息 -- 如果存在可靠的P2P通道(比如,基于TCP的P2P通道或基于增强UDP的P2P通道),则使用P2P通道发送,否则仍然通过服务器中转。  

SendByP2PChannel方法是当与目标用户之间有P2P通道存在时,即使其是不可靠的(比如普通的基于UDP的P2P通道),也一定采用P2P通道发送消息。所以,调用SendByP2PChannel方法发送目标消息,意味着,目标消息是可以被丢弃的。如果与目标用户之间的P2P通道不存在,那么将采取的操作取决于该方法的第4个参数ActionTypeOnNoP2PChannel的值,可以是通过服务器中转、也可以是丢弃消息。
  ActionTypeOnNoP2PChannel定义如下:

两个客户端之间有可能同时存在两个P2P通道:一个是TCP的,一个是UDP的。在这种情况下,ESFramework将优先使用基于TCP的P2P通道。

还记得前面我们介绍的ICustomizeOutter接口的TransferByServer方法,它的意思是,即使有可靠的P2P通道存在,信息也一定要通过服务器中转。

  顺便说一下,当我们采用前面介绍的IFileOutter来发送文件给其他在线用户时,ESFramework底层采用的一定是可靠的通道,以避免文件包丢失或其顺序发生错乱。

4.P2P通道控制器 IP2PController

  ESPlus为客户端提供了ESPlus.Application.P2PSession.Passive. IP2PController接口以控制和管理P2P通道。

  通过RapidPassiveEngine暴露了P2PController属性,我们可以获取IP2PController的引用。

  IP2PController接口的定义如下:

首先,我们可以通过设置P2PChannelMode属性,来要求ESPlus在尝试创建P2P通道时是使用UDP还是TCP,或者都进行尝试。

当我们要与某个其他在线用户P2P会话之前,可以先调用IP2PController的P2PConnectAsyn方法,该方法将会在后台线程中尝试与目标用户建立P2P连接(即进行UDP打洞和TCP打洞)。当P2P连接建立成功时,会触发P2PChannelOpened事件,而接下来后续的P2P消息就可以通过P2P通道发送。如果P2P连接建立失败,则将触发P2PConnectFailed事件。

  每一个P2P通道在内存中都对应着一个P2PChannelState实例,该实例记录着P2P通道的相关信息和实时状态,比如:P2P会话对方的UserID和地址信息,P2P通道的协议类型、通道的创建时间、通过该通道发送的消息个数、以及发送的最后一个消息的时间、当前通道是否可靠等。P2PChannelState的类图如下:

ESFramework 开发手册(04)--可靠的P2P_第2张图片

   当已经建立的P2P通道关闭时(可能是因为对方下线、或者P2P连接中断、或者UDP的P2P心跳超时),IP2PController将触发P2PChannelClosed的事件。
  任何时候,我们都可以通过IP2PController的IsP2PChannelExist方法查询与目标用户之间是否存在P2P通道。我们还可以通过其GetP2PChannelState方法来获取所有的或某个特定的P2P通道的实时状态。

5.四大武器小结

到现在为止,四大武器就全部介绍完毕了,现在我们以表格的形式,将前面介绍的四大武器涉及的API和回调接口整理一下。

ESFramework 开发手册(04)--可靠的P2P_第3张图片

API是框架已经为我们实现好了的,像所有以Outter和Controller结尾的组件,我们可以直接拿来使用。而所有以Handler结尾的接口,是我们要在程序中根据项目的具体需求来实现的,将这些实现的类注入到框架中,供框架回调(通过客户端和服务端Rapid引擎的Initialize方法的对应参数传入) - - 如此,框架就可以带动整个业务流程运转起来了。

最后,大家可以查看P2P的demo的源码,并运行demo,来尝试一下P2P。谢谢。

阅读 更多ESFramework开发手册系列文章

-----------------------------------------------------------------------------------------------------------------------------------------------

下载免费版本的ESFramework 以及 demo源码

关于ESFramework的任何问题,欢迎联系我们:

电话:027-87638960

Q Q:372841921

你可能感兴趣的:(framework)