P2P直连?经服务器中转?

      当同一个系统的两个客户端A、B相互发送消息给对方时,如果它们之间存在P2P通道,那么消息传送的路径就有两种:直接经P2P通道传送、或者经服务器中转。如下图所示:

     

      通常就一般应用而言,如果P2P通道能够成功创建(即所谓的打洞成功),A和B之间的所有消息将直接走P2P通道,这样可以有效节省服务器的带宽和降低服务器的负载。这种模型即是所谓的“P2P通道优先”模型,也是最简单的通道选择模型。

一.通道质量优先模型

      然而,有些系统可能不能就如此简单的处理,最简单的例子,如果A和B之间传递的某些类型的消息必需让服务器监控到,那么,这样的消息就必需经过服务器中转。接下来,我们讨论一种较为复杂的情况。比如,在网络语音对话系统中,通道的质量直接决定着用户体验的好坏。我们希望,在这种系统中,语音数据需要始终经由两条通道中的那个质量较高的通道进行传送。这种策略就是所谓的“通道质量优先”模型。

      “通道质量优先”模型理解起来很简单,但是在实际中实现时,却还是很有难度的。通常有两种实现方式:

(1)定时检测、比较通道延时,并自动切换通道。

(2)由上层应用决定何时切换通道。一般而言,是当应用发现当前使用的通道不满足要求时,就主动要求切换到另外一条通道。

二.模型实现

      下面,我们就基于ESFramework提供的通信功能,来实现这两种方式。我们使用AgileP2PCustomizeOutter类来封装它,并可通过属性控制来启用哪种方式。 

public class AgileP2PCustomizeOutter:IEngineActor
    {
        //字典。userID - 当前选择的通道(如果为true,表示P2P通道;否则是经服务器中转)?
        private ObjectManager<string, bool> channelStateManager = new ObjectManager<string, bool>();
       private ICustomizeOutter customizeOutter; //消息发送器
        private IP2PController p2PController;//P2P控制器
        private IBasicOutter basicOutter; //心跳发送器
        private AgileCycleEngine agileCycleEngine; //定时检测引擎

        #region PingTestSpanInSecs
        private int pingTestSpanInSecs = 60;
        /// <summary>
        /// 定时进行ping测试以及自动切换通道的时间间隔,单位:秒。默认值为60。
        /// 如果设置为小于等于0,则表示不进行定时ping测试,也不会自动切换通道。
        /// </summary>
        public int PingTestSpanInSecs
        {
            get { return pingTestSpanInSecs; }
            set { pingTestSpanInSecs = value; }
        }
        #endregion   

        #region Initialize
        public void Initialize(ICustomizeOutter _customizeOutter, IP2PController _p2PController, IBasicOutter _basicOutter)
        {
            this.customizeOutter = _customizeOutter;
            this.p2PController = _p2PController;
            this.basicOutter = _basicOutter;

            this.p2PController.P2PChannelOpened += new ESBasic.CbGeneric<P2PChannelState>(p2PController_P2PChannelOpened);
            this.p2PController.P2PChannelClosed += new ESBasic.CbGeneric<P2PChannelState>(p2PController_P2PChannelClosed);
            this.p2PController.AllP2PChannelClosed += new ESBasic.CbGeneric(p2PController_AllP2PChannelClosed);
            Dictionary<string, P2PChannelState> dic = this.p2PController.GetP2PChannelState();
            foreach (P2PChannelState state in dic.Values)
            {
                bool p2pFaster = this.TestSpeed(state.DestUserID);
                this.channelStateManager.Add(state.DestUserID, p2pFaster);
            }

            if (this.pingTestSpanInSecs > 0)
            {
                this.agileCycleEngine = new AgileCycleEngine(this);
                this.agileCycleEngine.DetectSpanInSecs = this.pingTestSpanInSecs;
                this.agileCycleEngine.Start();
            }
        }

        //定时执行,当前客户端到其它客户端之间的通道选择
        public bool EngineAction()
        {
            foreach (string userID in this.channelStateManager.GetKeyList())
            {
                bool p2pFaster = this.TestSpeed(userID);
                this.channelStateManager.Add(userID, p2pFaster);
            }

            return true;
        }

        void p2PController_AllP2PChannelClosed()
        {
            this.channelStateManager.Clear();
        }

        void p2PController_P2PChannelClosed(P2PChannelState state)
        {
            this.channelStateManager.Remove(state.DestUserID);
        }

        void p2PController_P2PChannelOpened(P2PChannelState state)
        {
            bool p2pFaster = this.TestSpeed(state.DestUserID);
            this.channelStateManager.Add(state.DestUserID, p2pFaster);
        }
        #endregion

        #region TestSpeed
        /// <summary>
        /// 定时测试
        /// </summary>
        private bool TestSpeed(string userID)
        {
            try
            {
                int transfer = this.basicOutter.PingByServer(userID);
                int p2p = this.basicOutter.PingByP2PChannel(userID);
                return p2p <= transfer;
            }
            catch (Exception ee)
            {
                return false;
            }
        }
        #endregion

        /// <summary>
        /// 手动切换通道。
        /// </summary>
        public void SwitchChannel(string destUserID)
        {
            if (this.channelStateManager.Contains(destUserID))
            {
                bool p2p = this.channelStateManager.Get(destUserID);
                this.channelStateManager.Add(destUserID, !p2p);
            }
        }

        /// <summary>
        /// 到目标用户是否使用的是P2P通道。
        /// </summary>
        public bool IsUsingP2PChannel(string destUserID)
        {
            return this.channelStateManager.Get(destUserID);
        }

        public bool IsExistP2PChannel(string destUserID)
        {
            return this.channelStateManager.Contains(destUserID);
        }

        /// <summary>
        /// 向在线用户发送信息。
        /// </summary>
        /// <param name="targetUserID">接收消息的目标用户ID。</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="post">是否采用Post模式发送消息</param>
        /// <param name="action">当通道繁忙时所采取的动作</param>
        public void Send(string targetUserID, int informationType, byte[] info, bool post, ActionTypeOnChannelIsBusy action)
        {
            bool p2pFaster = this.channelStateManager.Get(targetUserID);
            ChannelMode mode = p2pFaster ? ChannelMode.ByP2PChannel : ChannelMode.TransferByServer;
            this.customizeOutter.Send(targetUserID, informationType, info, post, action, mode);
        }

        /// <summary>
        /// 向在线用户或服务器发送大的数据块信息。直到数据发送完毕,该方法才会返回。如果担心长时间阻塞调用线程,可考虑异步调用本方法。
        /// </summary>
        /// <param name="targetUserID">接收消息的目标用户ID。如果为null,表示接收者为服务器。</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="blobInfo">大的数据块信息</param>
        /// <param name="fragmentSize">分片传递时,片段的大小</param>
        public void SendBlob(string targetUserID, int informationType, byte[] blobInfo, int fragmentSize)
        {
            bool p2pFaster = this.channelStateManager.Get(targetUserID);
            ChannelMode mode = p2pFaster ? ChannelMode.ByP2PChannel : ChannelMode.TransferByServer;
            this.customizeOutter.SendBlob(targetUserID, informationType, blobInfo, fragmentSize, mode);
        }
    }

       现在,我们对上面的实现简单解释一下。

(1)由于当前客户端可能会与多个其它的客户端进行通信,而与每一个其它的客户端之间的通信都有通道选择的问题,所以需要一个字典ObjectManager将它们管理起来。

(2)当某个P2P通道创建成功时,将进行首次ping比较,并将结果记录到字典中。

(3)定时引擎每隔60秒,分别针对每个其它客户端进行通道检测比较,自动选择ping值小的那个通道。

(4)当我们将PingTestSpanInSecs设为0时,就可以使用SwitchChannel方法来手动切换通道,即实现了上述的方式2。

(5)我们最终的目的是实现Send方法和SendBlob方法,之后,就可以使用AgileP2PCustomizeOutter类来替换ICustomizeOutter发送消息。

三.方式选择

      上面讲到“通道质量优先”模型的两种实现方式,那么在实际的应用中,如何进行选择了?

1.ping检测比较,自动切换     

      就这种方式而言,其缺陷在于,在客户端之间需要进行高频通信的系统中,ping检测可能是非常不准确的,甚至是错误的。

      比如,在实时视频对话系统中,其对带宽的要求是比较高的,假设,现在所有的视频数据走的都是P2P通道,那么P2P通道就非常忙碌,而经服务器中转的通道几乎就是空闲的。所以,当下一次定时ping检测到来时,P2P通道的ping值就会比实际的大。从而导致判断失误,而发生错误的自动切换。

2.手动切换

      对于刚才视频对话的例子,使用手动切换可能是更好的选择,由应用根据上层的实际效果来决定是否需要切换通道。比如,还以视频对话系统为例,应用可以根据信息接收方的定时反馈(在一段时间内,缺少音/视频包的个数,音/视频包的总延时等统计信息)来决定是否要切换到另外一个通道。这种方式更简洁描述可以表达为:如果当前通道质量已达到应用需求,即使另一个通道更快更稳定,也不进行切换;如果当前通道质量达不到应用需求,则切换到另一个通道(有可能另一个通道的质量更糟糕)。 

      本文只是简单地引出通道选择模型的问题,实际上,这个问题是相当复杂的,特别是在一些通信要求很高的项目中,而且,如果将广播消息的通道模型考虑进来就更麻烦了,有兴趣的朋友可以留言进行更深入的讨论。

 

 

时间: 2024-07-28 20:36:17

P2P直连?经服务器中转?的相关文章

tcp服务器中转,服务器转发怎么实现

问题描述 tcp服务器中转,服务器转发怎么实现 两个不同局域网的客户端A.B,一个公网服务器,A多线程向服务器发送文件,服务器收到文件转发给B,请问服务器端转发该怎么实现,需要同时传送多个文件 解决方案 A.B同时连接上服务器S,S记录下A.B的标记,接收A的文件后,找到B然后转发,标记可以用句柄,也可以用ip + 端口.编程用socket编程.socket编程基本资料参考http://www.ibm.com/developerworks/cn/education/linux/l-sock/l-

无须SMTP服务器中转直接发送电子邮件

前言 大家一定熟悉Foxmail中的"特快专递",它能直接将电子邮件发送到对方的邮件服务器中,而不需要经过SMTP服务器中转,这样做有什么好处?第一:发送速度比较快,不需要等SMTP服务器对邮件进行查毒.派发.验证:第二:你可以及时掌握邮件是否发送成功的信息.有时我们用Outlook发送一封邮件,到第二天对方都没收到,可我这边确实已经发送成功了,只好让对方多收几次,到了第三天SMTP服务器回信说"不好意思,你发往XXX的邮件因为XXX原因未能送达--",原来邮件被打

SOS!!!基于服务器中转的语音聊天问题

问题描述 在做聊天软件,在语音聊天这块,因为我的聊天是基于服务器中转的,所以我的想法是:当点击"语音"按钮时:客户端开始进行一直录音(直到用户点击结束语音聊天),并且每1024byte就存入一个.wav文件中,然后把这个文件发给服务器,让服务器再转发给另一个客户,再播放出来.下面是客户端一直录音并且每1024字节就保存起来的程序,但不知为什么,录到的文件始终为空.AudioFormataf;TargetDataLineline;Filevoicefile=newFile("E

tcp服务器中转,服务器端转发如何实现

问题描述 不同局域网的两个客户端(A.B)实现文件传输,一个服务器端(在公网内),想实现客户端A向服务器端同时发送多个文件,服务器端收到后,同时转发给客户端B 解决方案 解决方案二:为什么要"同时发送"多个文件一个一个顺序发送不就完了受带宽限制,即使你同时发送,总时间其实都一样的解决方案三:至于转发如果你要实现"离线文件",那就服务器先保存到本地,什么时候B要求接收文件,服务器再把文件发送给B呗如果要在线转发,A发送给服务器的数据包,服务器直接发给B呗解决方案四:是

呱呱视频分享创业经:10万元买个教训

图为呱呱视频社区副总裁董冠杰 DoNews 7月21日特稿(记者 肖克锋)2006年11月25日,在北京回龙观一间小屋里,张宏涛.董冠杰.王永强三人面面相觑,他们没想到自己掏出来的10万元积蓄竟然打了个水漂.三人合计了一下,还得出去给别人打工. 他们那时正在做一款视频语音聊天软件.虽然这款软件2006年上线一个月,同时在线用户就达到2万人,但他们却没赚到一分钱. 10万元打水漂 当初他们还是一腔热血.2003年时,张宏涛.董冠杰供职于UC.2004年7月,UC被新浪网收购,由于项目组未被新浪内部

免费分享:必应 广域网P2P 点对点 即时通讯系统(含打洞服务器、客户端) 可自建互联网通讯平台!

问题描述 免费分享:必应广域网P2P点对点即时通讯系统(含打洞服务器.客户端)可自建互联网通讯平台!最新:Bing1.0必应网络正式版,正式发布含打洞服务器程序,可自建互联网通讯平台!(含完整使用说明)下载地址1:下载地址2:详细说明:无需安装.无需登录.可局域网内,也可以跨互联网P2P通讯,在Windows各个版本均测试通过!内网通讯采用UDP方式,内外网(不同网段)之间通信完全采用P2PSocket点对点方式,信息不通过服务器中转,信息传递绝对安全高效,值得信赖!无需登录注册,运行即可使用,

两个虚拟SMTP服务器防止垃圾邮件中转的总结

smtp|服务器 在论坛上看到了很多高手的发言,结合自己在实践中的体会,终于有了一些比较成型的经验: 首先要说的是,想要彻底防止垃圾邮件制造者利用你的Exchange服务器进行中转,以及其他非法利用你的SMTP服务的现象,两个虚拟SMTP服务器是必不可少的. 这看起来是很基础的结论,却是我的"血泪"之言.因为我以前一直依靠一个虚拟服务器和一个SMTP连接器,进行SMTP限制,并且一直自信很有成效,结果前些日子被ISP警告:我的服务器成为垃圾邮件中转服务器! 现在具体说说我之前的设置,供

webrtc学习: 部署stun和turn服务器

webrtc的P2P穿透部分是由libjingle实现的.  步骤顺序大概是这样的:  1. 尝试直连. 2. 通过stun服务器进行穿透 3. 无法穿透则通过turn服务器中转.    stun 服务器比较简单. 网上也有很多公开的stun服务器可以用于测试. 例如 stun.ideasip.com 这里需要注意一下. 我在做Android应用时. 在少数老旧的手机上出现过一个bug:  PeerConnection close时非常慢. 大概需要50~80s.  后来反复检查, 才发现问题出

浏览器如何充当P2P软件

  浏览器充当P2P软件 作为一个可以上网的电脑,浏览器自然是一款标配的软件,所以如果利用浏览器直接进行文件传输,就可以省去安装使用其他第三方软件的麻烦了. 首先我们通过浏览器登录Reep.io网站(https://reep.io),这个网站不需要用户进行任何的注册就可以操作.现在点击网页中的"Add or drop flies"按钮,在弹出的对话框中选择需要进行发送的文件即可(图1).当然还有一个更简单的方法,就是选中文件以后拖拽到网页中释放.稍等片刻,网页中即会出现一个文件的分享链