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.手动切换
对于刚才视频对话的例子,使用手动切换可能是更好的选择,由应用根据上层的实际效果来决定是否需要切换通道。比如,还以视频对话系统为例,应用可以根据信息接收方的定时反馈(在一段时间内,缺少音/视频包的个数,音/视频包的总延时等统计信息)来决定是否要切换到另外一个通道。这种方式更简洁描述可以表达为:如果当前通道质量已达到应用需求,即使另一个通道更快更稳定,也不进行切换;如果当前通道质量达不到应用需求,则切换到另一个通道(有可能另一个通道的质量更糟糕)。
本文只是简单地引出通道选择模型的问题,实际上,这个问题是相当复杂的,特别是在一些通信要求很高的项目中,而且,如果将广播消息的通道模型考虑进来就更麻烦了,有兴趣的朋友可以留言进行更深入的讨论。
P2P直连?经服务器中转?的更多相关文章
- 挂接P2P通道-- ESFramework 4.0 进阶(08)
最新版本的ESFramework/ESPlus提供了基于TCP和UDP的P2P通道,而无论我们是使用基于TCP的P2P通道,还是使用基于UDP的P2P通道,ESPlus保证所有的P2P通信都是可靠的. ...
- TCP 进阶
转自: https://www.cnblogs.com/caoyusongnet/p/9087633.html 一. 端口号 标准的端口号由 Internet 号码分配机构(IANA)分配.这组数字被 ...
- 【计算机网络】 网络体系结构分类: 客户机/服务器体系和P2P
网络体系结构的分类 现代网络应用程序有两种主流的体系结构: 客户机/服务器体系结构和P2P体系结构(peer to peer “对等”) 一 . 客户机/服务器体系结构 客户机/服务器体系 ...
- rabbitmq 对多服务器p2p模式配置的一个测试
一直对rabbitmq p2p 模式的多服务器下做相同配置的 各个服务器数据接受情况比较好奇 今天有空测试了下 xml 文件 <?xml version="1.0" enco ...
- 2019-11-1-asp-dotnet-core-简单开发P2P中央服务器
title author date CreateTime categories asp dotnet core 简单开发P2P中央服务器 lindexi 2019-11-01 19:40:33 +08 ...
- [转]webrtc学习: 部署stun和turn服务器
[转]webrtc学习: 部署stun和turn服务器 http://www.cnblogs.com/lingdhox/p/4209659.html webrtc的P2P穿透部分是由libjingle ...
- c# p2p 穿透(源码加密)
http://blog.oraycn.com/ESFramework_Demo_P2P.aspx 测试,完全OK! 我很喜欢这个.可以源码是加密的!我希望实现 web 版本的p2p视频观看,aehy ...
- P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解
1.内容概述 P2P即点对点通信,或称为对等联网,与传统的服务器客户端模式(如下图"P2P结构模型"所示)有着明显的区别,在即时通讯方案中应用广泛(比如IM应用中的实时音视频通信. ...
- webrtc学习: 部署stun和turn服务器
webrtc的P2P穿透部分是由libjingle实现的. 步骤顺序大概是这样的: 1. 尝试直连. 2. 通过stun服务器进行穿透 3. 无法穿透则通过turn服务器中转. stun 服务器比较简 ...
随机推荐
- 关于java集合排序
对于排序,java开发者并不陌生. 为避免以后遗忘,现在再次总结一下! 常见8大排序算法, 平时自己熟悉的只有几种种!冒泡,二分/折半.插入.快排等!现在一一讲解一下,这里只讲思想,暂时不做实现! 一 ...
- centos7配置开启无线网卡,重启防火墙
centos7配置无线网卡: 在虚拟机为nat的网络连接下(就是默认的那个),centos7默认网卡未激活. 可以设置 文件 /etc/sysconfig/network-scripts/ifcfg- ...
- java基础第三天
- html5 拖拽文件到页面实现上传
思路:监听拖拽区域的 drop 事件,阻止浏览器上的默认拖拽事件 参考:http://www.helloweba.com/view-blog-192.html 例子: <!DOCTYPE htm ...
- C# IDisposable的理解
C#里可以嵌入非托管代码,这就涉及到了这些代码资源的释放.以前总是看到别人的代码里那么写,也没有好好想想为什么,今天看了书,总结一下. 资源释放分为两种: 托管的 非托管的 两者的释放方式不一致: 没 ...
- CSU 1810 Reverse
湖南省第十二届大学生计算机程序设计竞赛$H$题 规律,递推. 这种问题一看就有规律.可以按位统计对答案的贡献.即第$1$位对答案作出了多少贡献,第$2$位对答案作出了多少贡献.....累加和就是答案. ...
- HTTP 错误 404.8 - Not Found
HTTP 错误 404.8 - Not Found请求筛选模块被配置为拒绝包含 hiddenSegment 节的 URL 中的路径. 详细错误信息模块 RequestFilteringModule 通 ...
- ViewCompat.animate(view) 动画的操作
ViewCompat.animate(view) 实现动画的操作,动画兼容包; ViewCompat.animate(view) 实现动画的操作 .setDuration(200) .scaleX(0 ...
- GoogleCodeJam
2016年没有参赛,在师兄的介绍下,试了一下简单的一题,需要注意的是读写数据的形式还有具体代码. 2016资格赛 A题 Problem Bleatrix Trotter the sheep has d ...
- [ An Ac a Day ^_^ ] [kuangbin带你飞]专题十二 HDU 1176 免费馅饼
题意: 中文题意不解释…… 思路: 先把x,T存到矩阵里 然后像数塔一样从最底层走一边就行了 dp[i][j]代表在时间为j时 第i个位置最多能吃到多少个馅饼 最后输出第0时刻的5位置的馅饼数量就好了 ...