GGTalk 开源即时通讯系统源码剖析之:聊天消息防错漏机制

ggtalk · 浏览次数 : 0

小编点评

本文主要介绍了GGTalk客户端的全局缓存、本地存储以及聊天消息的防错漏机制。文章首先概述了客户端聊天消息的存储方式,然后详细讲解了GGTalk如何处理离线消息以及如何在多端之间自动同步消息。最后,文章总结了聊天消息防错漏机制的重要性,并鼓励读者点赞支持。 1. **客户端聊天消息存储**: - GGTalk客户端在本地缓存所有相关聊天消息,包括一对一聊天和群聊。 - 使用Sqlite数据库存储聊天记录,通过SqliteChatRecordPersister类进行消息的插入和查询。 - 客户端收到新消息时,触发MessageReceived方法进行处理。 2. **离线消息处理**: - 当用户离线时,发送给该用户的消息存储在OfflineMessage表中。 - 客户端在登录时向服务器请求离线消息。 - 服务器根据客户端的请求,从OfflineMessage表中提取并推送给客户端。 3. **多端自动同步**: - 服务端在收到需要转发的消息时,会判断接收方是否在线。 - 如果接收方在线,服务端会调用SendMessage方法将消息发送给对方的所有设备。 - 客户端通过预定事件监听器RapidPassiveEngine_EchoMessageReceived,实现在其他设备上接收到消息的通知。 总的来说,GGTalk通过一系列的设计和实现,确保了聊天消息的可靠性、完整性和安全性,这对于即时通讯软件来说至关重要。

正文

继上篇《GGTalk 开源即时通讯系统源码剖析之:客户端全局缓存及本地存储》GGTalk客户端的全局缓存以及客户端的本地持久化存储。接下来我们将介绍GGTalk的聊天消息防错漏机制。

GGTalk V8.0 对消息的可靠性,即消息的不丢失和不重复做了一系列优化处理,以保证不会错漏消息。

这篇文章将会详细的介绍GGTalk聊天消息防错漏机制。还没有GGTalk源码的朋友,可以到 GGTalk源码下载中心 下载。

一.  客户端聊天消息存储

GGTalk 在客户端本地缓存了所有与自己相关的聊天消息,包括1对1的聊天消息、和自己所属群的群聊消息。

消息缓存在本地的Sqlite数据库中,使用 SqliteChatRecordPersister 类向Sqlite中插入和查询聊天消息。

关于这部分的代码位于

GGTalk/GGTalk/TalkBase.Client/Core/ClientHandler.cs

GGTalk/TalkBase/Core/IChatRecordPersister.cs 

当客户端收到来自其它用户发送的消息时,会触发ClientHandler类中的rapidPassiveEngine_MessageReceived方法

void rapidPassiveEngine_MessageReceived(string sourceUserID, ClientType clientType, int informationType, byte[] info, string tag)
{ 
    //收到文字消息
    if (informationType == this.resourceCenter.TalkBaseInfoTypes.Chat)
    { 
   //逻辑处理 }
}

之后再通过SqliteChatRecordPersister的父类DefaultChatRecordPersister中的InsertChatMessageRecord方法存储聊天消息: 

/// <summary>
///
插入一条聊天记录。 /// </summary> public int InsertChatMessageRecord(ChatMessageRecord record) { if (this.transactionScopeFactory == null) { return 0; } object identity = 0; using (TransactionScope scope = this.transactionScopeFactory.NewTransactionScope()) { IOrmAccesser<ChatMessageRecord> accesser = scope.NewOrmAccesser<ChatMessageRecord>(); accesser.Insert(record); scope.Commit(); } return (int)record.AutoID;
}

当需要查看历史聊天记录时,GGTalk客户端会首先查询本地Sqlite数据库,这样就大大地减轻了服务器和数据库的压力,而且也减少了服务器的带宽占用。

接下来的问题是,离线消息要如何处理了?比如A用户不在线时,有好友发了消息给他,那么,当A上线时,是如何不错漏的获取到这些消息的?

二. GGTalk 是如何处理离线消息的?

回想一下,《GGTalk 开源即时通讯系统源码剖析之:数据库设计》中,我们介绍了OfflineMessage 表,即离线消息表,当目标用户不在线时,发送给他的消息存在该表中。

客户端在登录成功时,会向服务器请求离线消息。服务端收到该请求后,则从OfflineMessage 表中提取与它相关的离线消息推送给他。

关于这部分的代码位于

客户端:

GGTalk/GGTalk/TalkBase.Client/Core/ClientOutter.cs

GGTalk/GGTalk/TalkBase.Client/Core/ClientHandler.cs

服务端:

GGTalk/TalkBase/Server/Core/ServerHandler.cs

GGTalk/GGTalk.Server/DBPersister.cs

GGTalk/TalkBase/Server/Application/OfflineMemoryCache.cs

1. 客户端离线消息处理

每次登录或断线重连成功后,都会通过ClientOutter类中的RequestOfflineMessage方法向服务端请求离线消息,

/// <summary>
/// 请求离线消息
/// </summary>
public void RequestOfflineMessage()
{
    this.rapidPassiveEngine.CustomizeOutter.Send(this.talkBaseInfoTypes.GetOfflineMessage, null);
    this.rapidPassiveEngine.CustomizeOutter.Send(this.talkBaseInfoTypes.GetGroupOfflineMessage, null);
}

在ClientHandler类中的HandleInformation方法收到服务端返回的离线消息后,进行相应的处理

if (informationType == this.resourceCenter.TalkBaseInfoTypes.OfflineMessage)
{
    //逻辑处理
}

 2.服务端离线消息处理

在ServerHandler类中的rapidServerEngine_MessageReceived方法收到需要转发给其他用户的消息时,会先判断接收方是否在线,如果不在线的话,会通过IDBPersister接口中的StoreOfflineMessage方法将消息存储起来。当收到客户端请求离线消息时,则会调用PickupOfflineMessage将提取的目标用户的所有离线消息发送给对方。目前服务端中有三个类实现了此接口,分别是DBPersister(真实数据库)、DBPersister_SqlSugar(数据库是Oracle时使用)和OfflineMemoryCache(虚拟数据库)

/// <summary>
/// 存储离线消息。
/// </summary>       
/// <param name="msg">要存储的离线消息</param> 
void StoreOfflineMessage(OfflineMessage msg);

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

在离线消息的问题解决之后,还剩下一个与消息可靠性相关的难题,那就是当同一个账号同时登录到多个设备时(比如PC和手机),消息是如何在多端之间自动同步的了?

三. 聊天消息是如何在多端之间自动同步的?

这个问题可以拆解为两部分:

(1)作为发送方:我在某一设备上发送给好友的消息,如何同步到我登录的其它设备上?

(2)作为接收方:好友发给我的消息,如何发送给我登录的多个设备?

关于这部分的代码位于

客户端: 

GGTalk/GGTalk/TalkBase.Client/Core/ClientHandler.cs

服务端:

GGTalk/TalkBase/Server/Core/ServerHandler.cs

1. 发送方的消息同步

客户端在ClientHandler中预定IRapidPassiveEngine.EchoMessageReceived事件,当(当前用户在其它客户端设备上发送了消息)时,就会触发此事件。

/// <summary>
/// 初始化客户端消息处理器。
/// </summary>
/// <param name="center">资源中心</param>
/// <param name="icon">支持闪动的托盘。允许为null</param>
public void Initialize(ResourceCenter<TUser, TGroup> center, TwinkleNotifyIcon icon)
{
    this.resourceCenter = center;           
    this.twinkleNotifyIcon = icon;
    this.brige4ClientOutter = (IBrige4ClientOutter)this.resourceCenter.ClientOutter;

    this.resourceCenter.RapidPassiveEngine.MessageReceived += new CbGeneric<string, ClientType,int, byte[], string>(rapidPassiveEngine_MessageReceived);
    this.resourceCenter.RapidPassiveEngine.EchoMessageReceived += new CbGeneric<ClientType, string, int, byte[], string>(RapidPassiveEngine_EchoMessageReceived);
    this.resourceCenter.RapidPassiveEngine.ContactsOutter.BroadcastReceived += new CbGeneric<string,ClientType, string, int, byte[] ,string>(ContactsOutter_BroadcastReceived);            
}

//clientType - destUserID - informationType - message - tag 。
void RapidPassiveEngine_EchoMessageReceived(ClientType clientType, string destUserID, int informationType, byte[] info, string tag)
{
}

2.接收方的消息同步

服务端在ServerHandler类的rapidServerEngine_MessageReceived方法收到需要转发给其他用户的消息时,会先判断接收方是否在线,如果在线的话,会调用IRapidServerEngine.SendMessage方法将消息发送给对方的所有设备,来保证同一账号不同设备之间消息的同步。

void rapidServerEngine_MessageReceived(string sourceUserID, ClientType sourceType, int informationType, byte[] info, string tag)
{ 
    if (informationType == this.talkBaseInfoTypes.Chat)
    {
        string destID = tag;
        if (this.rapidServerEngine.UserManager.IsUserOnLine(destID))
        {
            this.rapidServerEngine.SendMessage(sourceType, destID, informationType, info, sourceUserID);
        } 
    }
}

 四. 结语

以上就是关于GGTalk聊天消息防错漏机制设计与实现的核心了。聊天消息防错漏机制在保障信息准确性、完整性和安全性方面发挥着重要作用,所以,作为一款即时通讯软件,实现该机制是绝对必要的。

如果你觉得还不错,请点赞支持啊!下篇再见!

与GGTalk 开源即时通讯系统源码剖析之:聊天消息防错漏机制相似的内容: