P2P直连?经服务器中转?

2024-05-15 07:32
文章标签 服务器 p2p 直连 中转

本文主要是介绍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 PingTestSpanInSecsprivate int pingTestSpanInSecs = 60;/// <summary>/// 定时进行ping测试以及自动切换通道的时间间隔,单位:秒。默认值为60。/// 如果设置为小于等于0,则表示不进行定时ping测试,也不会自动切换通道。/// </summary>public int PingTestSpanInSecs{get { return pingTestSpanInSecs; }set { pingTestSpanInSecs = value; }}    #endregion   #region Initializepublic 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.手动切换

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

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

这篇关于P2P直连?经服务器中转?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/991197

相关文章

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

部署Vue项目到服务器后404错误的原因及解决方案

《部署Vue项目到服务器后404错误的原因及解决方案》文章介绍了Vue项目部署步骤以及404错误的解决方案,部署步骤包括构建项目、上传文件、配置Web服务器、重启Nginx和访问域名,404错误通常是... 目录一、vue项目部署步骤二、404错误原因及解决方案错误场景原因分析解决方案一、Vue项目部署步骤

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

JavaWeb-WebSocket浏览器服务器双向通信方式

《JavaWeb-WebSocket浏览器服务器双向通信方式》文章介绍了WebSocket协议的工作原理和应用场景,包括与HTTP的对比,接着,详细介绍了如何在Java中使用WebSocket,包括配... 目录一、概述二、入门2.1 POM依赖2.2 编写配置类2.3 编写WebSocket服务2.4 浏

查询SQL Server数据库服务器IP地址的多种有效方法

《查询SQLServer数据库服务器IP地址的多种有效方法》作为数据库管理员或开发人员,了解如何查询SQLServer数据库服务器的IP地址是一项重要技能,本文将介绍几种简单而有效的方法,帮助你轻松... 目录使用T-SQL查询方法1:使用系统函数方法2:使用系统视图使用SQL Server Configu

nginx-rtmp-module构建流媒体直播服务器实战指南

《nginx-rtmp-module构建流媒体直播服务器实战指南》本文主要介绍了nginx-rtmp-module构建流媒体直播服务器实战指南,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有... 目录1. RTMP协议介绍与应用RTMP协议的原理RTMP协议的应用RTMP与现代流媒体技术的关系2

mysqld_multi在Linux服务器上运行多个MySQL实例

《mysqld_multi在Linux服务器上运行多个MySQL实例》在Linux系统上使用mysqld_multi来启动和管理多个MySQL实例是一种常见的做法,这种方式允许你在同一台机器上运行多个... 目录1. 安装mysql2. 配置文件示例配置文件3. 创建数据目录4. 启动和管理实例启动所有实例

VScode连接远程Linux服务器环境配置图文教程

《VScode连接远程Linux服务器环境配置图文教程》:本文主要介绍如何安装和配置VSCode,包括安装步骤、环境配置(如汉化包、远程SSH连接)、语言包安装(如C/C++插件)等,文中给出了详... 目录一、安装vscode二、环境配置1.中文汉化包2.安装remote-ssh,用于远程连接2.1安装2

Window Server创建2台服务器的故障转移群集的图文教程

《WindowServer创建2台服务器的故障转移群集的图文教程》本文主要介绍了在WindowsServer系统上创建一个包含两台成员服务器的故障转移群集,文中通过图文示例介绍的非常详细,对大家的... 目录一、 准备条件二、在ServerB安装故障转移群集三、在ServerC安装故障转移群集,操作与Ser

NFS实现多服务器文件的共享的方法步骤

《NFS实现多服务器文件的共享的方法步骤》NFS允许网络中的计算机之间共享资源,客户端可以透明地读写远端NFS服务器上的文件,本文就来介绍一下NFS实现多服务器文件的共享的方法步骤,感兴趣的可以了解一... 目录一、简介二、部署1、准备1、服务端和客户端:安装nfs-utils2、服务端:创建共享目录3、服