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

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

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

一. P2P 打洞

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

1. UDP打洞

   就目前我们常用的路由器、防火墙等NAT设备来说,大多都是Cone型(Full Cone、Restricted Cone、Port Restricted Cone 之一)的,所以UDP打洞的成功率还是非常大的(70%以上)。

   基于UDP的P2P打洞成功率与NAT设备的类型的关系一览表如下所示:

     

  从列表可以看出,最麻烦的是Symmetric类型,如果需要P2P通信的双方有一方是Symmetric,那么打洞就需要用到端口预测技术,而且其打洞成功的希望非常渺茫。

  大家可以通过从网上下载NAT类型的检测程序(比如STUN)来检测自己路由器等NAT设备的类型,以此来确定通信的双方基于UDP的P2P通道是否可以创建成功。 

2. TCP打洞

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

   即使如此,ESFramework支持基于TCP的P2P还是非常必要的,因为在以下两种情况下,基于TCP的P2P通道肯定是可以成功创建的。

(1)通信的双方位于同一个局网内。

(2)通信的双方中至少有一方运行于具有公网IP的机器上。

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

二. P2P 通道的可靠性

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

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

三. 通道选择

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

     public interface ICustomizeOutter
    {            
        void Send(string targetUserID, int informationType, byte[] info);

     void SendCertainly(string targetUserID, int informationType, byte[] info);     

        byte[] Query(string targetUserID, int informationType, byte[] info);

        /// <summary>
        /// 通过P2P通道(即使是不可靠的)向在线用户targetUserID发送信息。
        /// </summary>
        /// <param name="targetUserID">接收消息的目标用户ID</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="info">信息</param>  
        /// <param name="actionType">当P2P通道又不存在时,采取的操作</param>
        void SendByP2PChannel(string targetUserID, int informationType, byte[] info ,ActionTypeOnNoP2PChannel actionType);       
    }

      除SendByP2PChannel方法外,其它的方法都将使用可靠的P2P通道来发送消息,如果通信双方的P2P通道没有创建成功,则消息将通过服务器中转。  

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

       ActionTypeOnNoP2PChannel定义如下:

    public enum ActionTypeOnNoP2PChannel        
    {
        /// <summary>
        /// 通过服务器中转
        /// </summary>
        TransferByServer = 0,

        /// <summary>
        /// 丢弃消息
        /// </summary>
        Discard
    }

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

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

    顺便说一下,当我们采用前面介绍的IFileOutter来发送文件给其他在线用户时,如果存在P2P通道,则ESFramework将通过P2P通道来发送文件数据块。

四. P2P通道控制器 IP2PController 

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

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

  IP2PController接口的定义如下:

    public interface IP2PController
    {
         ///<summary>
         /// 当尝试建立P2P连接失败时,触发此事件。参数为对方的UserID。
         ///</summary>
         event CbGeneric<string> P2PConnectFailed;

         ///<summary>
         /// 当某个P2P Channel创建成功时,触发此事件。
         ///</summary>
         event CbGeneric<P2PChannelState> P2PChannelOpened;

         ///<summary>
         /// 当某个P2P Channel关闭时,触发此事件。参数为对方的UserID。
         ///</summary>
         event CbGeneric<P2PChannelState> P2PChannelClosed;

         ///<summary>
         /// 当使用可靠UDP的P2P通道时,是否开启PMTU自动发现。默认状态为关闭。
         ///</summary>
         bool PMTUDiscoveryEnabled { get; set; }

         ///<summary>
         /// 采用的P2P通道的类型。默认为TcpAndUdp,表示TCP打洞和UDP打洞都进行尝试。
         ///</summary>
         P2PChannelMode P2PChannelMode { get; set; }
 
         ///<summary>
         /// 尝试与目标用户建立P2P Channel。(异步方式。)
         ///</summary>
         ///<param name="destUserID">目标用户的UserID</param>
         void P2PConnectAsyn(string destUserID);
 
         ///<summary>
         /// 与目标用户之间是否存在P2P通道。
         ///</summary>
         bool IsP2PChannelExist(string destUserID);
 
         ///<summary>
         /// P2P通道是否繁忙。如果返回null,表示没有P2P通道,或者不了解P2P通道的繁忙状态(当tcp通道接入时或使用未增强的UDP通道)。
         ///</summary>
         bool? P2PChannelIsBusy(string destUserID);
 
         ///<summary>
         /// 获取所有P2P通道的状态。
         ///</summary>
         Dictionary<string, P2PChannelState> GetP2PChannelState();
 
         ///<summary>
         /// 获取目标用户的P2P通道的状态。
         ///</summary>
         P2PChannelState GetP2PChannelState(string destUserID);
   }
 
   ///<summary>
   /// P2P通道模型。
   ///</summary>
   public enum P2PChannelMode
   {
       TcpAndUdp = 0,
       Tcp,
       Udp
   }

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

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

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

  

  当已经建立的P2P通道关闭时(可能是因为对方下线、或者P2P连接中断、或者UDP的P2P心跳超时),IP2PController将触发P2PChannelClosed的事件。

  任何时候,我们都可以通过IP2PController的IsP2PChannelExist方法查询与目标用户之间是否存在P2P通道。我们还可以通过其GetP2PChannelState方法来获取所有的或某个特定的P2P通道的实时状态。      

       您可以查看P2P的demo的源码(ESFramework Demo -- P2P通信Demo(附源码),并运行demo,来尝试一下P2P。

 

下一篇:ESFramework 开发手册(05) -- 好友与组

上一篇:ESFramework 开发手册(03) -- 文件(夹)传送

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

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

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

Q Q:168757008

官网:www.oraycn.com

导航

首页

官方网站

立即咨询 

站内搜索

ESFramework 通信框架

详细说明

SDK下载

ESFramework FAQ

版本变更记录

OMCS 语音视频框架

详细说明

SDK下载

OMCS FAQ

版本变更记录

OrayTalk 企业即时通讯系统

详细说明

客户端下载

傲瑞实用组件

SDK下载

NPush 消息推送组件

StriveEngine 轻量级的通信引擎

MFile 语音视频录制组件

MCapture 语音视频采集组件

MPlayer 语音视频播放组件

OAUS 自动升级系统

傲瑞组件 FAQ

授权

授权流程

产品授权说明

产品选购指南

授权SDK使用说明

其它

SDK使用技巧

联系我们

电话:027-87638960

Q Q:168757008

邮件:master@oraycn.com