ESFramework 使用技巧 -- 大数据块信息
在一般的通信系统中,我们都是通过网络来传递尺寸较小的单个信息(数十个字节,最大也就几K或几十K),但是有时候,需要传递的信息的个头很大,比如在内存中产生的一个数据报表,可能有数M之大。我们将这样的大尺寸信息称为“大数据块信息”(Blob)。
一.发送Blob的几种方案
在ESFramework/ESPlus中,发送的自定义信息的最大尺寸是有限制的,这个限制源于框架对单个消息的最大尺寸有限制(默认为100K,可以通过GlobalUtil的SetMaxLengthOfMessage静态方法进行修改)。 在之前的版本中,如果要发送巨大尺寸的消息,有以下几个方案:
(1)修改框架允许的单个消息的最大尺寸的默认值,比如从100K改为10M。
(2)通过文件中转。即将要发送的大尺寸信息先存储到一个文件中, 然后使用ESPlus提供的传送文件的功能将其传递给对方。
(3)在发送方可以手动拆分信息,逐个发送信息片段,然后在接收方将接收到的片段拼接为一个完整的信息。
我们应当极力避免方案(1)的做法。我们建议,最好不要通过单个消息来发送一个巨大的Blob。虽然,TCP连接允许我们一次性将一个大尺寸的数据交给它去发送,但是对某些系统而言,这样做是很危险的。为什么了?因为当这个大尺寸的数据还没有发送完毕时,那么,在该TCP连接上继续发送任何其它数据,都会被阻塞。这意味着,如果大尺寸的数据发送需要30s,那么在这30秒内,整个通道都被Blob独占,无法发送任何其他数据,包括可能必须的紧急命令信息。
而且,方案(1)需要框架预留更多的内存作为数据缓冲区,在某些时候,这可能是一种很大的浪费。
方案(2)涉及到硬盘IO,而且以文件作为中转,不够直观。
方案(3)是最佳的,Blob的片段可以和其他的信息交叉发送。但是,手动实现该方案还是比较繁琐的。幸运的是在最新的ESPlus 3.0 中,已经内置了该方案的实现。
二.ESPlus 3.0 提供的发送Blob方法
ESPlus 3.0在自定义信息空间提供了发送Blob的几个API:
1.ICustomizeOutter的SendBlob方法:用于在客户端将Blob信息发送给其他在线用户或服务器。
/// <summary>
/// 向在线用户或服务器发送大的数据块信息。直到数据发送完毕,该方法才会返回。如果担心长时间阻塞调用线程,可考虑异步调用本方法。
/// </summary>
/// <param name="targetUserID">接收消息的目标用户ID。如果为null,表示接收者为服务器。</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="blobInfo">大的数据块信息</param>
/// <param name="fragmentSize">分片传递时,片段的大小</param>
void SendBlob(string targetUserID, int informationType, byte[] blobInfo ,int fragmentSize);
2.ICustomizeController的SendBlob方法:用于在服务端将Blob信息发送给某个在线用户。
/// <summary>
/// 向ID为userID的在线用户发送大数据块信息。直到数据发送完毕,该方法才会返回。如果担心长时间阻塞调用线程,可考虑异步调用本方法。
/// </summary>
/// <param name="userID">接收消息的用户ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="blobInfo">大数据块信息</param>
/// <param name="fragmentSize">分片传递时,片段的大小</param>
void SendBlob(string userID, int informationType, byte[] blobInfo, int fragmentSize);
对于发送Blob信息,要注意以下几点:
(1)选择合适的片段大小(fragmentSize)。fragmentSize的参数的值取决于网络的畅通状态和我们期望的单个片段发送所需的时间。
(2)无论是发送方、还是接收方都需要将Blob信息当作一个整体。在传送Blob片段的过程中,如果接收方或发送方任何一方掉线,则整个Blob信息的传送都会被取消。接收方决不会提交不完整的blob信息给自定义信息处理器(ICustomizeHandler)去处理。
(3)SendBlob方法是在当前调用线程中,逐个发送Blob片段的。如果Blob信息很大,而又不想阻塞调用线程,请使用异步调用。
我们以常见的发送图片为例,由于大一点的图片可能有几M,所以通常采用Blob分片段发送。我们使用异步调用的方式:
private RapidPassiveEngine rapidPassiveEngine = ...;
public static int ImageInformationType = 101;
public void SendImage(Image img, string destUserID)
{
MemoryStream memoryStream = new MemoryStream() ;
img.Save(memoryStream, img.RawFormat);
byte[] blob = memoryStream.ToArray();
memoryStream.Close();
CbGeneric<byte[], string> cb = new CbGeneric<byte[], string>(this.SendBlobThread);
cb.BeginInvoke(blob, destUserID, null, null);
}
private void SendBlobThread(byte[] blob ,string destUserID)
{
this.rapidPassiveEngine.CustomizeOutter.SendBlob(destUserID, ImageInformationType, blob, 2048);
}
(4)在接收方,无论接收的是普通自定义信息,还是Blob信息,都会提交给同一个方法(ICustomizeHandler.HandleInformation方法)处理,没有分别。
我们可以在接收方像下面这样处理刚才发送的图片:
public void HandleInformation(string sourceUserID, int informationType, byte[] info)
{
if (informationType == ImageInformationType)
{
MemoryStream memoryStream = new MemoryStream(info) ;
Image img = Image.FromStream(memoryStream);
memoryStream.Close();
//...... 后续的业务逻辑
}
}
三.Blob信息与P2P
如果收发Blob信息的双方是两个在线的用户,并且这两个用户之间已经建立了P2P通道,那么Blob信息将直接经过P2P通道传送,而不经过服务器中转。这和普通的自定义信息是没有分别的。(关于如何在两个客户端之间创建P2P通道,请参考ESFramework 开发手册(04) -- 可靠的P2P )
Q Q:168757008