GGTalk 開源即時通訊系統原始碼剖析之:聊天訊息防錯漏機制

C#开源即时通讯GGTalk發表於2024-07-02

繼上篇《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聊天訊息防錯漏機制設計與實現的核心了。聊天訊息防錯漏機制在保障資訊準確性、完整性和安全性方面發揮著重要作用,所以,作為一款即時通訊軟體,實現該機制是絕對必要的。

如果你覺得還不錯,請點贊支援啊!下篇再見!

相關文章