ESFramework 使用技巧 -- 实现离线消息

      在ESFramework 开发手册(01) -- 发送和处理信息一文中,我们介绍了如何使用ESPlus.Application.CustomizeInfo命名空间的组件来发送和处理自定义消息。而在实际的项目中,需要实现离线消息的功能是一个常见的需求,也有很多客户来咨询如何做才能实现离线消息,所以,在这里,我们简单介绍一下使用ESFramework/ESPlus实现离线消息的原理与步骤。 

一.如何截获离线消息  

  我们已经知道,一个在线用户给另一个用户发送二进制信息采用的是ESPlus.Application.CustomizeInfo.Passive.ICustomizeOutter接口的Send方法,如:

        /// <summary>
        
/// 向在线用户targetUserID发送二进制信息。如果目标用户不在线,则服务端会调用ICustomizeInfoBusinessHandler.OnTransmitFailed方法来通知应用程序。
        
/// </summary>
        
/// <param name="targetUserID">接收消息的目标用户ID</param>
        
/// <param name="informationType">自定义信息类型</param>
        
/// <param name="info">二进制信息</param>      
        void Send(string targetUserID, int informationType, byte[] info);       

  对于类似在线用户发给其他用户的P2P类型的消息,可以通过P2P通道发送,也可以通过服务器中转。当目标用户不在线时,P2P通道肯定是不存在的,所以消息一定是提交到服务器。服务器接收到要转发的P2P消息时,判断目标用户是否在线,如果在线,则直接转发;否则,框架会触发ESPlus.Application.CustomizeInfo.Server.ICustomizeController接口的TransmitFailed事件:

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

      我们只要预定ICustomizeController接口的这个事件就可以监控到所有的离线消息了。 

二.离线消息的管理

  截获到离线消息后,我们可能需要将其存到数据库(或其它地方),然后,等到目标用户上线的时候,再从数据库中提取属于该用户的离线消息发送给他即可。

  首先,我们需要对离线消息做一个封装 -- OfflineMessage

    [Serializable]
    
public class OfflineMessage
    {
        
#region Ctor
        
public OfflineMessage() { }
        
public OfflineMessage(string _sourceUserID, string _destUserID, int _informationType, byte[] info)
        {
            
this.sourceUserID = _sourceUserID;
            
this.destUserID = _destUserID;
            
this.informationType = _informationType;
            
this.information = info;
        } 
        
#endregion

        
#region SourceUserID
        
private string sourceUserID = "";
        
/// <summary>
        
/// 发送离线消息的用户ID。
        
/// </summary>
        public string SourceUserID
        {
            
get { return sourceUserID; }
            
set { sourceUserID = value; }
        } 
        
#endregion

        
#region DestUserID
        
private string destUserID = "";
        
/// <summary>
        
/// 接收离线消息的用户ID。
        
/// </summary>
        public string DestUserID
        {
            
get { return destUserID; }
            
set { destUserID = value; }
        } 
        
#endregion

        
#region InformationType
        
private int informationType = 0;
        
/// <summary>
        
/// 信息的类型。
        
/// </summary>
        public int InformationType
        {
            
get { return informationType; }
            
set { informationType = value; }
        } 
        
#endregion

        
#region Information
        
private byte[] information;
        
/// <summary>
        
/// 信息内容
        
/// </summary>
        public byte[] Information
        {
            
get { return information; }
            
set { information = value; }
        } 
        
#endregion      
   
        
#region Time
        
private DateTime time = DateTime.Now;
        
/// <summary>
        
/// 服务器接收到要转发离线消息的时间。
        
/// </summary>
        public DateTime Time
        {
            
get { return time; }
            
set { time = value; }
        } 
        
#endregion
    }

  接下来,我们定义IOfflineMessageManager接口,用于管理离线消息:

    /// <summary>
    
/// 离线消息管理器。
    
/// </summary>
    public interface IOfflineMessageManager
    {
        
/// <summary>
        
/// 存储离线消息。
        
/// </summary>       
        
/// <param name="msg">要存储的离线消息</param>
        void Store(OfflineMessage msg);

        
/// <summary>
        
/// 提取目标用户的所有离线消息。
        
/// </summary>       
        
/// <param name="destUserID">接收离线消息用户的ID</param>
        
/// <returns>属于目标用户的离线消息列表,按时间升序排列</returns>
        List<OfflineMessage> Pickup(string destUserID);
    }

  实现这个接口,我们便可以将离线消息存储到数据库或文本或网络等等,然后等到需要时再次从中提取。 

三.存储离线消息

  有了IOfflineMessageManager接口,我们便可以处理ICustomizeController接口的TransmitFailed事件了:

        private IOfflineMessageManager offlineMessageManager = ......;
        
public void OnTransmitFailed(Information information)
        {
            
OfflineMessage msg = new OfflineMessage(information.SourceID, information.DestID, information.InformationType, information.Content);
            
this.offlineMessageManager.Store(msg);
        }

  我们也许并不需要将所有的离线消息都存储起来,有些不重要的离线消息可以丢弃,而只保存那些我们关心的消息。这只需要在存储消息之前加一个条件判断进行过滤即可。 

四.提取并发送离线消息

  我们已经知道,可以通过IUserManager的SomeOneConnected事件来得知某个用户上线了,于是,我们可以在该事件处理函数中,提取属于该用户的离线消息并一一发送给他。我们通过类似下面的代码来做到这一点。

    public class OfflineMessageBridge
    {
        
#region UserManager
        
private IUserManager userManager;
        
public IUserManager UserManager
        {
            
set { userManager = value; }
        } 
        
#endregion

        
#region OfflineMessageManager
        
private IOfflineMessageManager offlineMessageManager;
        
public IOfflineMessageManager OfflineMessageManager
        {
            
set { offlineMessageManager = value; }
        } 
        
#endregion

        
#region CustomizeController
        
private ICustomizeController customizeController;
        
public ICustomizeController CustomizeController
        {
            
set { customizeController = value; }
        } 
        
#endregion

        
public void Initialize()
        {
            
this.userManager.SomeOneConnected += new CbGeneric<ESFramework.Server.UserManagement.UserData>(userManager_SomeOneConnected);
        }           

        
void userManager_SomeOneConnected(ESFramework.Server.UserManagement.UserData userData)
        {
            
List<OfflineMessage> list = this.offlineMessageManager.Pickup(userData.UserID);
            
if (list != null && list.Count > 0)
            {
                
foreach (OfflineMessage msg in list)
                {
                   
byte[] bMsg = CompactPropertySerializer.Default.Serialize<OfflineMessage>(msg);
                    
this.customizeController.Send(msg.DestUserID, 
InfoTypes.OfflineMessage , bMsg);
                }
            }
        }
    }

  当用户上线时,会将属于他的离线消息按照时间的顺序一一发送给他(demo中,离线消息的类型为InfoTypes.OfflineMessage,您可以自定义它)。当然,你也可以将属于他的所有离线消息打成一个包,一次性发送也可以。如果是这样,你就需要再增加一条自定义的信息类型和相关的协议类了。 

      要注意的是,在服务端触发SomeOneConnected事件时,虽然对应的客户端已经完成了登录,但是,其可能还未完成全部的初始化动作,此时,客户端可能就无法正确地处理收到的离线消息。如何解决这种问题了?可以换种稳妥的方案:服务端并不预定SomeOneConnected事件,而客户端了,在完成初始化之后,向服务端发送一个请求消息(InfoTypes.GetOfflineMessage),服务端在接收到这个请求消息后,才将离线消息发送给它。

五.在客户端处理离线消息

  根据上面第三点的描述,我们知道,当客户端上线时,客户端会接收到服务端发过来的属于当前客户端的离线消息。所以,客户端在实现的ICustomizeHandler接口的HandleInformation方法需要增加对离线消息的处理:    

      public void HandleInformation(string sourceUserID, int informationType, byte[] info)
        {
            if (informationType == InfoTypes.OfflineMessage)
            {
                OfflineMessage msg = CompactPropertySerializer.Default.Deserialize<OfflineMessage>(info, 0);
                this.HandleInformation(msg.SourceUserID, msg.InformationType, msg.Information);
                return;
            }
       ............................ 
     } 

      Demo处理中,采用了一种偷懒的方式,将离线消息解析后,直接再次递归调用了HandleInformation方法 -- 就好像是现在刚收到了来自其它客户端的消息一样,忽略了原始的发送时间(OfflineMessage的Time属性)。如果不能忽略原始的发送时间,您可以采取更合适的处理方式。

六.小结

  从上面可以看出,基于ESFramework/ESPlus实现离线消息策略是相当简单的,最主要的焦点有两个:第一是可以通过处理ICustomizeController接口的TransmitFailed事件来截获到所有的离线消息;第二是通过IUserManager的SomeOneConnected事件就能知道用户上线的时刻。

  有的朋友可能会问离线文件又该怎么实现了?实际上也是同样的原理,只不过要多用到ESPlus.Application.FileTransfering命名空间下的一些类来完成文件的收发功能,这个以后我们再介绍。

  本文只是实现离线消息的一个简单示例,在实际的应用中,可能需要做更多的工作来满足项目的具体需要,这里就不再一一赘述了。

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

Q Q:168757008

官网:www.oraycn.com

导航

首页

官方网站

联系我们

立即咨询 

站内搜索

OrayTalk 企业即时通讯系统

傲瑞通官网

详细说明

客户端下载

OrayMeeting 视频会议系统

详细说明

客户端下载

ESFramework 通信框架

详细说明

SDK与Demo下载

ESFramework FAQ

版本变更记录

OMCS 语音视频框架

详细说明

SDK与Demo下载

OMCS FAQ

版本变更记录

OVCS 视频会议Demo

详细说明

源码下载

傲瑞实用组件

SDK下载

H5Media 纯网页音视频交互

NPusher 推流组件

MCapture 语音视频采集组件

MFile 语音视频录制组件

MPlayer 语音视频播放组件

OAUS 自动升级系统

StriveEngine 轻量级的通信引擎

傲瑞组件 FAQ

授权

授权流程

产品选购指南

授权方案说明

授权SDK使用说明

其它

支持信创国产化

SDK使用技巧

联系我们