ESFramework 开发手册(01) -- 发送和处理信息

      本文介绍ESFramework 开发手册(00) -- 概述一文中提到的四大武器的第一个:发送和处理自定义信息。

      使用通信框架最基础的需求就是收发信息,ESFramework底层已经为我们封装好了所有与信息收发相关的操作,我们只要调用ESPlus.Application.CustomizeInfo命名空间下的相关组件的API来发送信息,以及实现对应的处理器接口来处理收到的信息就可以了。  

一.客户端发送信息  

       客户端可以发送信息给服务端,也可以发送信息给其他在线用户。

      客户端通过ESPlus.Application.CustomizeInfo.Passive.ICustomizeOutter接口提供的方法来发送信息。

      我们可以从ESPlus.Rapid.IRapidPassiveEngine暴露的CustomizeOutter属性来获取ICustomizeOutter引用。

    public interface ICustomizeOutter
    {
        /// <summary>
        /// 向服务器发送信息。
        /// </summary>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="info">信息</param>
        void Send(int informationType, byte[] info);

        /// <summary>
        /// 向在线用户targetUserID发送信息。
        /// </summary>
        /// <param name="targetUserID">接收消息的目标用户ID</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="info">信息</param>      
        void Send(string targetUserID, int informationType, byte[] info);

        /// <summary>
        /// 向服务器提交请求信息,并返回服务器的应答信息。如果超时没有应答则将抛出Timeout异常。
        /// </summary>      
        /// <param name="informationType">自定义请求信息的类型</param>
        /// <param name="info">请求信息</param>
        /// <returns>服务器的应答信息</returns>
        byte[] Query(int informationType, byte[] info);

        /// <summary>
        /// 向在线用户或服务器发送信息。
        /// </summary>
        /// <param name="targetUserID">接收消息的目标用户ID。如果为null,表示接收者为服务器。</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="post">是否采用Post模式发送消息</param>  
        /// <param name="action">当通道繁忙时所采取的动作</param>  
        void Send(string targetUserID, int informationType, byte[] info, bool post, ActionTypeOnChannelIsBusy action);   

        /// <summary>
        /// 即使与目标用户之间有可靠的P2P通道存在,也要通过服务器转发信息。
        /// </summary>
        /// <param name="targetUserID">接收消息的目标用户ID,不能为null。</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="info">信息</param>         
        void TransferByServer(string targetUserID, int informationType, byte[] info );   

        /// <summary>
        /// 向在线用户或服务器发送信息,并等待其ACK。当前调用线程会一直阻塞,直到收到ACK;如果超时都没有收到ACK,则将抛出TimeoutException。
        /// </summary>
        /// <param name="targetUserID">接收消息的目标用户ID。如果为null,表示信息接收者为服务端。</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="info">信息</param>      
        void SendCertainly(string targetUserID, int informationType, byte[] info);

        /// <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);       
  }

发送信息有几种方式:

1. 普通发送:

      调用Send方法进行普通发送,即将信息写入网络流后就立即返回。

      Send方法的重载有个ActionTypeOnChannelIsBusy参数,用于指示当通道繁忙时所采取的动作:继续发送、或丢弃数据。在某些系统中,对于一些非重要非紧急信息的发送,可以为ActionTypeOnChannelIsBusy参数传入枚举值Discard(丢弃)。

2. 带ACK机制的发送:

        调用SendCertainly方法发送信息时会启用ACK机制,即将信息发送出去后,调用并不返回,而是要等到接收方的ACK后,才返回。如果接收方不在线,SendCertainly调用会在阻塞一段时间后(默认为30秒),抛出超时异常。

       ACK机制是由ESFramework底层实现的,我们直接使用,不需要做任何额外的其它工作。关于带ACK机制的信息发送的更多内容可以参见ACK机制

3. 信息同步调用:

       调用Query方法可以发送请求信息,并返回接收方处理请求后的应答信息。就像方法调用一样 - - 使用参数调用方法并返回结果。从Query方法的重载看到,信息同步调用的对象既可以是服务端、也可以是另外一个在线客户端。关于信息同步调用的更多内容可以参见消息同步调用

4. 回复异步调用:

       重载的Query方法(带有CallbackHandler参数的)在发送请求信息后,不会阻塞而继续向下执行,而框架在收到对应的回复信息时,会回调CallbackHandler委托指向的方法。由于调用线程与回复回调的线程不是同一个线程,所以称这种机制为回复异步调用。

5. 使用P2P通道发送:

       调用SendByP2PChannel方法可以明确指定使用P2P通道进行发送,这是一种普通发送。如果与接收者之间的P2P通道不存在,则由ActionTypeOnNoP2PChannel参数指示如何动作:使用服务器转发、或者丢弃信息。关于P2P通道的更多内容可参考ESFramework 开发手册(04) -- 可靠的P2P

6. 强制经过服务器转发:

       对于某些类型的P2P信息,我们可能想在服务端监控它,如果这些信息还是经过P2P通道发送的话,那么服务端将捕获不到这些信息。TransferByServer方法用于解决这一问题。如果调用TransferByServer方法发送信息,那么即使有可靠的P2P通道存在,信息仍然会经过服务器中转。

7. 发送大数据块:

      调用SendBlob方法可以将大数据块信息(比如一张大图片)发送给服务端或任何其他的在线用户。 关于大数据块的更多内容,可以参考ESFramework 使用技巧 -- 大数据块信息。 

.服务端发送信息

        服务端可以通过ESPlus.Application.CustomizeInfo.Server.ICustomizeController接口向客户端发送信息和广播、以及同步调用客户端。

       我们可以从ESPlus.Rapid.IRapidServerEngine暴露的CustomizeController属性来获取ICustomizeController引用。 

  public interface ICustomizeController
    {
        /// <summary>
        /// 当因为目标用户不在线而导致服务端转发自定义信息失败时,将触发该事件。参数为转发失败的信息。
        /// </summary>
        event CbGeneric<Information> TransmitFailed;

        /// <summary>
        /// 向ID为userID的在线用户发送信息。如果用户不在线,则直接返回。
        /// </summary>
        /// <param name="userID">接收消息的用户ID</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="info">信息</param>
     /// <param name="clientType">要发给哪个终端设备。如果传入null,表示发送给所有在线终端设备</param>
     void Send(string userID, int informationType, byte[] info,ClientType? clientType = null);
        
        /// <summary>
        /// 向当前AS上的在线用户发送信息,并等待其ACK。当前调用线程会一直阻塞,直到收到ACK;如果超时都没有收到ACK,则将抛出Timeout异常。
        /// </summary>
        /// <param name="userID">接收消息的用户ID</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="info">信息</param> 
     /// <param name="ClientType">要发给哪个终端设备</param>          
     void SendCertainlyToLocalClient(string userID, int informationType, byte[] info, ClientType clientType);        

     /// <summary>
        /// 向ID为userID的在线用户发送信息。如果用户不在线,则直接返回。
        /// </summary>
        /// <param name="userID">接收消息的用户ID</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="info">信息</param>        
        /// <param name="post">是否采用Post模式发送消息</param>  
        /// <param name="action">当通道繁忙时所采取的动作</param>
     /// <param name="clientType">要发给哪个终端设备。如果传入null,表示发送给所有在线终端设备</param>
     void Send(string userID, int informationType, byte[] info ,bool post,ActionTypeOnChannelIsBusy action,ClientType? clientType = null);
        /// <summary>
        /// 向ID为userID的在线用户发送大数据块信息。直到数据发送完毕,该方法才会返回。如果担心长时间阻塞   用线程,可考虑异步调用本方法。
        /// </summary>
        /// <param name="userID">接收消息的用户ID</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="blobInfo">大数据块信息</param>
        /// <param name="fragmentSize">分片传递时,片段的大小</param>
     /// <param name="clientType">要发给哪个终端设备。如果传入null,表示发送给所有在线终端设备</param>
     void SendBlob(string userID, int informationType, byte[] blobInfo, int fragmentSize,ClientType? clientType = null);
     
     /// </summary>          
        /// 询问当前AS的在线用户,并返回应答信息。如果超时没有应答则将抛出Timeout异常。如果客户端在处理请求时出现未捕获的异常,则该调用会抛出HandingException。
        /// </summary>      
        /// <param name="userID">接收并处理服务器询问的目标用户ID</param>
        /// <param name="informationType">自定义请求信息的类型</param>
        /// <param name="info">请求信息</param>
        /// <<param name="clientType">要发给哪个终端设备</param>
     /// <returns>客户端给出的应答信息</returns>
     byte[] QueryLocalClient(string userID, int informationType, byte[] info, ClientType clientType);
    }

       各个方法含义几乎与客户端是一致的。ICustomizeController接口还暴露了TransmitFailed事件,我们可以通过预定该事件来监控那些转发失败的信息。

       SendCertainlyToLocalClient方法和QueryLocalClient方法的名称都以LocalClient结尾,其在ESPlatform群集平台中就会显示其特别的含义 - - 这两个方法只能针对连接到当前服务端的客户端进行调用。而其它的几个方法,则是可以将信息发送给任何在线用户的,即使该用户位于群集中的其它应用服务器上。  

三.处理信息

       客户端可以收到来自其它客户端或服务端的信息、大数据块、以及同步调用。服务端也可以收到来自客户端的信息(转发的信息除外)及同步调用。那么,我们如何处理这些接收到的信息了?

       无论是服务端,还是客户端,都只要实现ESPlus.Application.CustomizeInfo.ICustomizeHandler接口即可。 

  public interface ICustomizeHandler
    {
        /// <summary>
        /// 处理接收到的自定义信息。
        /// </summary>
        /// <param name="sourceUserID">发送该信息的用户ID。如果为null,表示信息发送者为服务端。</param>
        /// <param name="clientType">客户端设备类型</param>
        /// <param name="informationType">自定义信息类型</param>
        /// <param name="info">信息</param>
        void HandleInformation(string sourceUserID, ClientType clientType , int informationType, byte[] info);

        /// <summary>
        /// 处理接收到的请求并返回应答信息。
        /// </summary>
        /// <param name="sourceUserID">发送该请求信息的用户ID。如果为null,表示信息发送者为服务端。</param>     
        /// <param name="clientType">客户端设备类型</param>
        /// <param name="informationType">自定义请求信息的类型</param>  
        /// <param name="info">请求信息</param>
        /// <returns>应答信息</returns>
        byte[] HandleQuery(string sourceUserID, ClientType clientType, int informationType, byte[] info);
    }

(1)凡是sourceUserID参数为null的,都表示被处理的信息是来自服务端的;否则,表示被处理的信息是由其它在线客户端发出的。

(2)ICustomizeHandler 即用于客户端、也用于服务端。如果是用于服务端,则方法的第一个参数sourceUserID是绝对不会为null的。

(3)ICustomizeHandler接口的所有方法都是在后台线程中被调用的,所以如果这些方法的实现中涉及到了操作UI,一定要将调用转发到UI线程。

(4)在客户端,将ICustomizeHandler的实现类的实例传递给ESPlus.Rapid.IRapidPassiveEngine的Initialize方法以挂接到框架;

         在服务端,则将ICustomizeHandler的实现类的实例传递给ESPlus.Rapid.IRapidServerEngine的Initialize方法以挂接到框架。 

四.更多说明 

1. 信息发送模型

       信息发送可以使用同步模型或异步模型,在方法中通过bool型post参数体现出来。如果其值为true,表示使用异步模型(即发送方法的调用立即返回,不用等到信息发送完毕);否则使用同步模型(阻塞调用线程,直到信息发送完毕)。

2. 信息处理

      客户端和服务端的ICustomizeHandler,我们称之为自定义信息处理器,或者业务处理器,表示其用于处理我们应用系统的具体业务逻辑。

(1)业务处理器将在后台线程中被调用,所以,实现业务处理器的方法中如果涉及到了UI操作,则必须将调用转发到UI线程。

(2)业务处理器的方法必须尽可能快地返回,否则,将不能及时地处理后续的消息。如果某个业务处理方法非常耗时,可以考虑使用异步方式。

3. 大数据块

      当发送大数据块时,发送方会将其拆分为许多连续的片段逐个发送,而在接收方会自动将接收到的片段重组起来构成一个完整的信息。而且无论是发送大数据块,还是普通信息,在接收方都是调用相同的方法(ICustomizeHandler的HandleInformation方法)来处理的。      

五.另一套消息收发机制

      除了通过Customize空间发送消息和处理消息外,ESFramework的最新版本增加了另一套收发消息的机制 -- 直接通过引擎接口收发消息。

      与Customize机制的区别在于,通过引擎接口发送的消息,接收方将触发 MessageReceived 事件,而不是回调Handler接口。这两套机制是相互独立的,不会相互影响。下面我们把引擎接口中与消息收发相关的API摘出来:

  public interface IRapidPassiveEngine
    {
        /// <summary>
        /// 当接收到来自服务器或其它用户的消息时,触发此事件。
        /// 事件参数:sourceUserID - clientType - informationType - message - tag 。
        /// 如果消息来自服务器,则sourceUserID为null。
        /// </summary>
        event CbGeneric<string,ClientType,int, byte[], string> MessageReceived;
     
     /// <summary> /// 当(当前用户在其它客户端设备上发送了消息)时,触发此事件。 /// 事件参数:clientType - destUserID - informationType - message - tag 。 /// 如果原消息的接收者为服务器,则destUserID为null。 /// </summary> event CbGeneric<ClientType, string, int, byte[], string> EchoMessageReceived;      /// <summary> /// 向服务器或其它在线用户发送消息。如果其它用户不在线,消息将被丢弃。 /// </summary> /// <param name="targetUserID">接收者的UserID,如果为服务器,则传入null</param> /// <param name="informationType">消息类型</param> /// <param name="message">消息内容</param> /// <param name="tag">附加内容</param>      /// <param name="toOtherClientOfMine">是否发送给我的其它在线设备</param>      /// <param name="observer">消息发送进度的观察者</param> void SendMessage(string targetUserID, int informationType, byte[] message, string tag, bool toOtherClientOfMine = false,                IProgressObserver observer = null);    /// <summary> /// 向服务器或其它在线用户发送消息。如果其它用户不在线,消息将被丢弃。 /// </summary> /// <param name="targetUserID">接收者的UserID,如果为服务器,则传入null</param> /// <param name="informationType">消息类型</param> /// <param name="message">消息内容</param> /// <param name="tag">附加内容</param> /// <param name="fragmentSize">消息将被分块发送,分块的大小</param>      /// <param name="toOtherClientOfMine">是否发送给我的其它在线设备</param>      /// <param name="observer">消息发送进度的观察者</param> void SendMessage(string targetUserID, int informationType, byte[] message, string tag , int fragmentSize,bool toOtherClientOfMine = false,                 IProgressObserver observer = null); /// <summary> /// 向服务器或其它在线用户异步发送消息(当前调用线程立即返回)。如果其它用户不在线,消息将被丢弃。 /// </summary> /// <param name="targetUserID">接收者的UserID,如果为服务器,则传入null</param> /// <param name="informationType">消息类型</param> /// <param name="message">消息内容</param> /// <param name="tag">附加内容</param> /// <param name="fragmentSize">消息将被分块发送,分块的大小</param> /// <param name="handler">当发送任务结束时,将回调该处理器</param> /// <param name="handlerTag">将回传给ResultHandler的参数</param>      /// <param name="toOtherClientOfMine">是否发送给我的其它在线设备</param> void SendMessage(string targetUserID, int informationType, byte[] message, string tag, int fragmentSize, ResultHandler handler, object handlerTag,bool toOtherClientOfMine = false);}
   public interface IRapidServerEngine
    {
        /// <summary>
        /// 当接收到来自客户端的消息时,触发此事件。
        /// 事件参数:sourceUserID - sourceType - informationType - message - tag 。      
        /// </summary>
        event CbGeneric<string, ClientType,int, byte[], string> MessageReceived;
  
        /// <summary>
        /// 向在线用户发送消息。如果目标用户不在线,消息将被丢弃。
        /// </summary>
        /// <param name="targetUserID">接收者的UserID</param>
        /// <param name="informationType">消息类型</param>
        /// <param name="message">消息内容</param>
        /// <param name="tag">附加内容</param>
     /// <param name="clientType">要发送给哪个客户端设备,如果为null,则表示发给所有设备</param>
        void SendMessage(string targetUserID, int informationType, byte[] message, string tag, ClientType? clientType = null);

        /// <summary>
        /// 向在线用户发送消息。如果目标用户不在线,消息将被丢弃。
        /// </summary>
        /// <param name="targetUserID">接收者的UserID</param>
        /// <param name="informationType">消息类型</param>
        /// <param name="message">消息内容</param>
        /// <param name="tag">附加内容</param>
        /// <param name="fragmentSize">消息将被分块发送,分块的大小</param>
     /// <param name="clientType">要发送给哪个客户端设备,如果为null,则表示发给所有设备</param>
        void SendMessage(string targetUserID, int informationType, byte[] message, string tag, int fragmentSize,ClientType? clientType = null);
}

      比如,客户端通过IRapidPassiveEngine的SendMessage方法发送消息给服务端,则服务端的IRapidServerEngine将触发MessageReceived事件。预定MessageReceived事件,我们就可以处理接收到的消息。

       假设A登录了PC和安卓,然后A在安卓上通过SendMessage 发消息给B,并将 toOtherClientOfMine参数传入true时,则A的PC端会触发EchoMessageReceived事件。

      在实际使用时,究竟该使用哪种机制比较好了?

      我们的建议是,以Customize机制为主,以引擎直接收发消息机制为辅助。在Customize机制中,通过实现处理器接口,我们可以将所有的消息处理集中到一个Handler类中;而如果使用引擎直接收发消息的机制,则在很多地方都可以预定MessageReceived事件,使得消息处理的代码分散在不同的地方。

 

 

下一篇:ESFramework 开发手册(02) -- 在线用户管理、基础功能及状态通知

上一篇:ESFramework 开发手册(00) -- 概述

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

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

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

Q Q:168757008

官网:www.oraycn.com

导航

首页

官方网站

联系我们

立即咨询 

站内搜索

ESFramework 通信框架

详细说明

SDK与Demo下载

ESFramework FAQ

版本变更记录

OMCS 语音视频框架

详细说明

SDK与Demo下载

OMCS FAQ

版本变更记录

OrayTalk 企业即时通讯系统

详细说明

客户端下载

OVCS 视频会议系统

详细说明

源码下载

傲瑞实用组件

SDK下载

H5Media 纯网页音视频交互

NPusher 推流组件

MCapture 语音视频采集组件

MFile 语音视频录制组件

MPlayer 语音视频播放组件

OAUS 自动升级系统

StriveEngine 轻量级的通信引擎

傲瑞组件 FAQ

授权

授权流程

产品选购指南

授权方案说明

授权SDK使用说明

其它

支持信创国产化

SDK使用技巧

联系我们