開(kāi)源即時(shí)通訊GGTalk源碼剖析之:客戶端全局緩存及本地存儲(chǔ)
繼上篇《開(kāi)源國(guó)產(chǎn)即時(shí)通訊GGTalk源碼剖析之:虛擬數(shù)據(jù)庫(kù)》詳細(xì)介紹了 GGTalk 內(nèi)置的虛擬的數(shù)據(jù)庫(kù),無(wú)需部署真實(shí)數(shù)據(jù)庫(kù)便能體驗(yàn)GGTalk的全部功能,虛擬數(shù)據(jù)庫(kù)將極大地簡(jiǎn)化服務(wù)端的部署過(guò)程,能使服務(wù)端立即運(yùn)行起來(lái)。接下來(lái)我們將進(jìn)入GGTalk的客戶端,此篇將介紹GGTalk 客戶端全局緩存及本地存儲(chǔ)。
GGTalk V8.0?對(duì)需要頻繁請(qǐng)求服務(wù)器的數(shù)據(jù)做了客戶端全局緩存處理,大大減少了向服務(wù)器的請(qǐng)求次數(shù),降低了服務(wù)器的壓力,而且,這也使得客戶端的運(yùn)行速度更快、用戶操作體驗(yàn)更流暢。
這篇文章將會(huì)詳細(xì)的介紹GGTalk客戶端的全局緩存以及客戶端的本地持久化存儲(chǔ)。還沒(méi)有GGTalk源碼的朋友,可以點(diǎn)擊文章底部鏈接下載。
一. GGTalk 客戶端緩存設(shè)計(jì)
1. ClientGlobalCache類(lèi)
ClientGlobalCache 類(lèi)是GGTalk客戶端全局緩存的核心實(shí)現(xiàn),其代碼位置如下圖所示:

然后來(lái)到這個(gè)類(lèi)的定義:

這個(gè)類(lèi)的核心作用是在內(nèi)存中保存用戶和群組的數(shù)據(jù)。首先這個(gè)類(lèi)接受兩個(gè)泛型參數(shù),分別為TUser
和TGroup
,并且限定TUser
為引用類(lèi)型,并且需要實(shí)現(xiàn)TalkBase.IUser
接口,還要具有一個(gè)無(wú)參數(shù)的公共構(gòu)造函數(shù);限定TGroup
需要實(shí)現(xiàn)TalkBase.IGroup
接口,且要求具有一個(gè)無(wú)參數(shù)的公共構(gòu)造函數(shù)。除此之外,這個(gè)類(lèi)繼承自BaseGlobalCache<TUser, TGroup>類(lèi)(后面將詳細(xì)介紹)。
在 ClientGlobalCache 類(lèi)的實(shí)現(xiàn)里,首先我們可以看到三個(gè)私有字段的定義,其作用如下:
rapidPassiveEngine;
:rapid客戶端引擎,用于與rapid服務(wù)端引擎之間進(jìn)行通信。talkBaseHelper;
:工具方法調(diào)用器,由TalkBase類(lèi)庫(kù)約定方法的定義。talkBaseInfoTypes;
:客戶端與服務(wù)端進(jìn)行通信的消息的類(lèi)型。
緊接著,我們來(lái)到ClientGlobalCache類(lèi)構(gòu)造函數(shù)的實(shí)現(xiàn):
構(gòu)造函數(shù)接受五個(gè)參數(shù),其分別是:
engine
:rapid客戶端引擎。helper
:工具方法調(diào)用器。infoTypes
:消息類(lèi)型。persistenceFilePath
:數(shù)據(jù)緩存文件的目錄。logger
:日志記錄器。
在構(gòu)造函數(shù)方法體內(nèi),分別對(duì)ClientGlobalCache類(lèi)內(nèi)部定義的三個(gè)私有字段進(jìn)行了賦值,并且還調(diào)用Initialize方法
,這個(gè)方法的作用是什么呢?想要了解這個(gè)方法我們得先去了解ClientGlobalCache類(lèi)的父類(lèi)BaseGlobalCache,因?yàn)?Initialize 方法定義在其父類(lèi)里面。
2. BaseGlobalCache類(lèi)
我們找到 BaseGlobalCache 類(lèi)的源碼,接著查看關(guān)于這個(gè)類(lèi)的部分實(shí)現(xiàn):
首先我們能夠看到兩個(gè)字段,userManager
和groupManager
,它們作用分別是用來(lái)緩存用戶和群組的數(shù)據(jù),它們的類(lèi)型都是ObjectManager
(看到這里如果你了解GGTalk服務(wù)端虛擬數(shù)據(jù)庫(kù)設(shè)計(jì)和GGTalk服務(wù)端全局緩存的話,你會(huì)發(fā)現(xiàn)他們都用到了這個(gè)類(lèi)型),這里就不再贅述了。
二. GGTalk 客戶端本地持久化存儲(chǔ)
接下來(lái)我們?cè)賮?lái)看 BaseGlobalCache 的?originUserLocalPersistence
字段,這個(gè)字段的作用是將用戶和群組的數(shù)據(jù)緩存到本地文件。它的類(lèi)型是UserLocalPersistence<TUser, TGroup>
,來(lái)到定義:

在這個(gè)類(lèi)的內(nèi)部定義了四個(gè)屬性,分別為FriendList
、GroupList
、QuickAnswerList
和RecentList
,其代表含義如下:
FriendList
:好友列表;GroupList
:群組列表;QuickAnswerList
:快捷回復(fù)列表;RecentList
: 最近聯(lián)系人/群列表。
再接下來(lái)我們需要關(guān)注這個(gè)類(lèi)里面的兩個(gè)方法,也是這個(gè)類(lèi)的核心功能,分別是Load
和Save
方法。Load 方法
接受一個(gè)文件路徑作為參數(shù),將這個(gè)文件的內(nèi)容讀取出來(lái)并轉(zhuǎn)化為UserLocalPersistence<TUser, TGroup>
類(lèi)型;Save 方法
也是接受一個(gè)文件路徑作為參數(shù),將調(diào)用這個(gè)方法的對(duì)象轉(zhuǎn)化為byte[]
,并存入指定文件路徑的文件中。
以下是這兩個(gè)方法的實(shí)現(xiàn):
了解到這里,我想你應(yīng)該明白什么數(shù)據(jù)會(huì)被緩存到本地文件。沒(méi)錯(cuò),就是上述的四個(gè)屬性,分別是好友列表、群組列表、快捷回復(fù)列表和最近聯(lián)系人/群列表。
在了解完BaseGlobalCache類(lèi)的字段后,我們回到主題,來(lái)看關(guān)于Initialize 方法
的實(shí)現(xiàn):
由于本篇文章介紹的是客戶端全局緩存,故在此方法中的一些無(wú)關(guān)邏輯被有意隱藏,如果你想了解更完整的實(shí)現(xiàn),建議配合GGTalk源碼進(jìn)行閱讀。
來(lái)分析這段代碼,首先通過(guò)調(diào)用DoGetUser方法
,拿到當(dāng)前登錄的用戶數(shù)據(jù),然后通過(guò)userManager
將其緩存到內(nèi)存。接著接將本地緩存文件路徑保存到persistenceFilePath 字段
,接著通過(guò)調(diào)用UserLocalPersistence<TUser, TGroup>上的靜態(tài)方法Load
,讀取本地緩存文件的內(nèi)容。在本地緩存存在的情況下,去獲取本地緩存文件中的快捷回復(fù)列表、好友列表和群組列表,將快捷回復(fù)列表保存到quickAnswerList 字段
,并且將好友列表中的每一個(gè)好友的數(shù)據(jù)都通過(guò)userManager
保存到內(nèi)存中,將群組列表中的每一個(gè)群組的數(shù)據(jù)都通過(guò)groupManager
保存到內(nèi)存中。
綜上所述:Initialize 方法
的作用就是讀取關(guān)于當(dāng)前登錄用戶對(duì)應(yīng)的本地緩存文件的數(shù)據(jù),并將其保存到內(nèi)存中。
現(xiàn)在有了文件 ——> 數(shù)據(jù)
,那么數(shù)據(jù) ——> 文件
是在哪里實(shí)現(xiàn)的呢?還記得BaseGlobalCache類(lèi)的save 方法
嗎,我們順著引用查看最終將數(shù)據(jù)存入文件的代碼:
順著引用我們找到了SaveUserLocalCache 方法
,這個(gè)方法的作用就是將用戶的好友列表數(shù)據(jù)、群組列表數(shù)據(jù)和快捷回復(fù)列表數(shù)據(jù)存入本地緩存文件。這個(gè)方法是在?MainForm_FormClosing?方法中調(diào)用的。
看到這里就串起來(lái)了,即在客戶端窗體關(guān)閉時(shí),就會(huì)將好友列表、群組列表和快捷回復(fù)列表數(shù)據(jù)緩存到本地文件中。
三. 更新本地緩存
想象這樣一個(gè)場(chǎng)景,在某用戶離線期間,此用戶的好友或群組的信息發(fā)生了變更。比如,某好友資料發(fā)生了變化,或者有人從好友列表中刪除了他,或者它所在的群組加入或移除了新成員,等等。那么在這名用戶下次登錄時(shí),從本地存儲(chǔ)拿到的緩存數(shù)據(jù)必然就是老版本的,那么GGTalk是如何解決這個(gè)問(wèn)題的呢?
這里以好友列表數(shù)據(jù)為例(代碼在路徑GGTalk/GGTalk/TalkBase.Client/Core/BaseGlobalCache.cs
):
當(dāng)用戶登錄后窗體顯示時(shí),或斷線重連成功時(shí),此方法會(huì)被調(diào)用。這個(gè)方法的作用就是通過(guò)判斷緩存中是否存在用戶來(lái)決定刷新部分聯(lián)系人數(shù)據(jù)還是重新從服務(wù)器加載數(shù)據(jù)。
如果緩存中只有自己一個(gè)人,表示是第一次在該電腦上登錄,此時(shí)將執(zhí)行LoadContactsFromServer
方法以從服務(wù)器加載所有聯(lián)系人等數(shù)據(jù)。
如果緩存中只有多個(gè)人,表示不是第一次在該電腦上登錄,此時(shí)將執(zhí)行RefreshContactRTData
方法以更新本地?cái)?shù)據(jù)到最新版本。
在這里我們主要需要關(guān)注RefreshContactRTData
方法:
這個(gè)方法會(huì)先去獲取當(dāng)前登錄用戶在服務(wù)器上最新的聯(lián)系人列表數(shù)據(jù)(僅僅包含ID、版本號(hào)、狀態(tài)),然后去遍歷緩存中用戶的ID,檢查來(lái)自服務(wù)器最新的聯(lián)系人列表數(shù)據(jù)是否包含此ID對(duì)應(yīng)的用戶,若不包含則需要將此ID對(duì)應(yīng)的用戶從緩存中去除。接著再遍歷來(lái)自服務(wù)器上最新聯(lián)系人的用戶數(shù)據(jù),若此用戶不存在于本地緩存,則下載該用戶數(shù)據(jù)并將其加入緩存。接下來(lái)就是根據(jù)版本號(hào)比較來(lái)判斷聯(lián)系人的資料是否發(fā)生變化,若發(fā)生變化則將其同步到本地緩存。
四. 總結(jié)
GGTalk客戶端緩存流程:在用戶登錄后,首先會(huì)從本地緩存文件中讀取用戶的好友列表、群組列表和快捷回復(fù)列表數(shù)據(jù),將這些數(shù)據(jù)保存到內(nèi)存中。然后,從服務(wù)器獲取最新的聯(lián)系人版本信息,與本地緩存比較后,下載需要更新的聯(lián)系人資料。而當(dāng)客戶端窗口關(guān)閉,也就是退出登錄時(shí),會(huì)將該用戶的好友列表、群組列表和快捷回復(fù)列表數(shù)據(jù)緩存到本地文件。
以上就是關(guān)于GGTalk客戶端全局緩存設(shè)計(jì)的核心了,在接下來(lái)的一篇我們將介紹GGTalk中是如何收發(fā)消息及處理消息的。
敬請(qǐng)期待:《GGTalk 開(kāi)源即時(shí)通訊系統(tǒng)源碼剖析之:消息收發(fā)及處理》
點(diǎn)擊下面鏈接下載GGTalk 8.0版本源碼。
https://zhuanlan.zhihu.com/p/651560820?