继上篇《GGTalk 开源即时通讯系统源码剖析之:虚拟数据库》详细介绍了 GGTalk 内置的虚拟的数据库,无需部署真实数据库便能体验GGTalk的全部功能,虚拟数据库将极大地简化服务端的部署过程,能使服务端立即运行起来。接下来我们将进入GGTalk的客户端,此篇将介绍GGTalk 客户端全局缓存及本地存储。

GGTalk V8.0 对需要频繁请求服务器的数据做了客户端全局缓存处理,大大减少了向服务器的请求次数,降低了服务器的压力,而且,这也使得客户端的运行速度更快、用户操作体验更流畅。

这篇文章将会详细的介绍GGTalk客户端的全局缓存以及客户端的本地持久化存储。还没有GGTalk源码的朋友,可以到 GGTalk源码下载中心 下载。

一. GGTalk 客户端缓存设计

1. ClientGlobalCache类

ClientGlobalCache 类是GGTalk客户端全局缓存的核心实现,其代码位置如下图所示:

然后来到这个类的定义:



这个类的核心作用是在内存中保存用户和群组的数据。首先这个类接受两个泛型参数,分别为TUserTGroup,并且限定TUser为引用类型,并且需要实现TalkBase.IUser接口,还要具有一个无参数的公共构造函数;限定TGroup需要实现TalkBase.IGroup接口,且要求具有一个无参数的公共构造函数。除此之外,这个类继承自BaseGlobalCache<TUser, TGroup>类(后面将详细介绍)。

在 ClientGlobalCache 类的实现里,首先我们可以看到三个私有字段的定义,其作用如下:

  • rapidPassiveEngine;:rapid客户端引擎,用于与rapid服务端引擎之间进行通信。
  • talkBaseHelper;:工具方法调用器,由TalkBase类库约定方法的定义。
  • talkBaseInfoTypes;:客户端与服务端进行通信的消息的类型。

紧接着,我们来到ClientGlobalCache类构造函数的实现:

public ClientGlobalCache(IRapidPassiveEngine engine, ITalkBaseHelper<TGroup> helper, TalkBaseInfoTypes infoTypes, string persistenceFilePath, IAgileLogger logger) {
this.rapidPassiveEngine = engine;
this.talkBaseHelper = helper;
this.talkBaseInfoTypes = infoTypes;
this.Initialize(this.rapidPassiveEngine.CurrentUserID, persistenceFilePath, helper, logger);
}

构造函数接受五个参数,其分别是:

  • engine:rapid客户端引擎。
  • helper:工具方法调用器。
  • infoTypes:消息类型。
  • persistenceFilePath:数据缓存文件的目录。
  • logger:日志记录器。

在构造函数方法体内,分别对ClientGlobalCache类内部定义的三个私有字段进行了赋值,并且还调用Initialize方法,这个方法的作用是什么呢?想要了解这个方法我们得先去了解ClientGlobalCache类的父类BaseGlobalCache,因为 Initialize 方法定义在其父类里面。

2. BaseGlobalCache类

我们找到 BaseGlobalCache 类的源码,接着查看关于这个类的部分实现:

public abstract class BaseGlobalCache<TUser, TGroup>
where TUser : TalkBase.IUser
where TGroup : TalkBase.IGroup
{
//...
private ObjectManager<string, TUser> userManager = new ObjectManager<string, TUser>(); //缓存用户资料
private ObjectManager<string, TGroup> groupManager = new ObjectManager<string, TGroup>();
private UserLocalPersistence<TUser, TGroup> originUserLocalPersistence;
//...
}

首先我们能够看到两个字段,userManagergroupManager,它们作用分别是用来缓存用户和群组的数据,它们的类型都是ObjectManager(看到这里如果你了解GGTalk服务端虚拟数据库设计和GGTalk服务端全局缓存的话,你会发现他们都用到了这个类型),这里就不再赘述了。

二. GGTalk 客户端本地持久化存储

接下来我们再来看 BaseGlobalCache 的 originUserLocalPersistence字段,这个字段的作用是将用户和群组的数据缓存到本地文件。它的类型是UserLocalPersistence<TUser, TGroup>,来到定义:



在这个类的内部定义了四个属性,分别为FriendListGroupListQuickAnswerListRecentList,其代表含义如下:

  • FriendList:好友列表;
  • GroupList:群组列表;
  • QuickAnswerList:快捷回复列表;
  • RecentList: 最近联系人/群列表。

再接下来我们需要关注这个类里面的两个方法,也是这个类的核心功能,分别是LoadSave方法。Load 方法接受一个文件路径作为参数,将这个文件的内容读取出来并转化为UserLocalPersistence<TUser, TGroup>类型;Save 方法也是接受一个文件路径作为参数,将调用这个方法的对象转化为byte[],并存入指定文件路径的文件中。

以下是这两个方法的实现:

// 从文件读取数据
public static UserLocalPersistence<TUser, TGroup> Load(string filePath) {
try {
if (!File.Exists(filePath)) {
return null;
}
byte[] data = ESBasic.Helpers.FileHelper.ReadFileReturnBytes(filePath);
return (UserLocalPersistence<TUser, TGroup>)ESBasic.Helpers.SerializeHelper.DeserializeBytes(data, 0, data.Length);
}
catch {
return null;
}
}
// 将数据存储到文件
public void Save(string filePath) {
byte[] data = ESBasic.Helpers.SerializeHelper.SerializeObject(this);
ESBasic.Helpers.FileHelper.WriteBuffToFile(data, filePath);
}

了解到这里,我想你应该明白什么数据会被缓存到本地文件。没错,就是上述的四个属性,分别是好友列表、群组列表、快捷回复列表和最近联系人/群列表。

在了解完BaseGlobalCache类的字段后,我们回到主题,来看关于Initialize 方法的实现:

public virtual void Initialize(string curUserID, string persistencePath, IUnitTypeRecognizer recognizer, IAgileLogger _logger) {
//...
//自己的信息始终加载最新的
this.currentUser = this.DoGetUser(curUserID);
this.userManager.Add(this.currentUser.ID, this.currentUser);
this.persistenceFilePath = persistencePath;
this.originUserLocalPersistence = UserLocalPersistence<TUser, TGroup>.Load(this.persistenceFilePath);//返回null,表示该登录帐号还没有任何缓存
if (this.originUserLocalPersistence == null) {}
else {
this.quickAnswerList = this.originUserLocalPersistence.QuickAnswerList;
foreach (TUser user in this.originUserLocalPersistence.FriendList) {
if (user.ID == null) {
continue;
}
if (user.ID != this.currentUser.ID) {
user.UserStatus = UserStatus.OffLine;
user.CommentName = this.currentUser.GetUnitCommentName(user.ID);
this.userManager.Add(user.ID, user);
}
}
foreach (TGroup group in this.originUserLocalPersistence.GroupList) {
if (this.currentUser.GroupList.Contains(group.ID)) {
group.CommentName = this.currentUser.GetUnitCommentName(group.ID);
this.groupManager.Add(group.ID, group);
}
}
}
}

由于本篇文章介绍的是客户端全局缓存,故在此方法中的一些无关逻辑被有意隐藏,如果你想了解更完整的实现,建议配合GGTalk源码进行阅读。

来分析这段代码,首先通过调用DoGetUser方法,拿到当前登录的用户数据,然后通过userManager将其缓存到内存。接着接将本地缓存文件路径保存到persistenceFilePath 字段,接着通过调用UserLocalPersistence<TUser, TGroup>上的静态方法Load,读取本地缓存文件的内容。在本地缓存存在的情况下,去获取本地缓存文件中的快捷回复列表、好友列表和群组列表,将快捷回复列表保存到quickAnswerList 字段,并且将好友列表中的每一个好友的数据都通过userManager保存到内存中,将群组列表中的每一个群组的数据都通过groupManager保存到内存中。

综上所述:Initialize 方法的作用就是读取关于当前登录用户对应的本地缓存文件的数据,并将其保存到内存中。

现在有了文件 ——> 数据,那么数据 ——> 文件是在哪里实现的呢?还记得BaseGlobalCache类save 方法吗,我们顺着引用查看最终将数据存入文件的代码:

顺着引用我们找到了SaveUserLocalCache 方法,这个方法的作用就是将用户的好友列表数据、群组列表数据和快捷回复列表数据存入本地缓存文件。这个方法是在 MainForm_FormClosing 方法中调用的。

看到这里就串起来了,即在客户端窗体关闭时,就会将好友列表、群组列表和快捷回复列表数据缓存到本地文件中。

三. 更新本地缓存

想象这样一个场景,在某用户离线期间,此用户的好友或群组的信息发生了变更。比如,某好友资料发生了变化,或者有人从好友列表中删除了他,或者它所在的群组加入或移除了新成员,等等。那么在这名用户下次登录时,从本地存储拿到的缓存数据必然就是老版本的,那么GGTalk是如何解决这个问题的呢?

这里以好友列表数据为例(代码在路径GGTalk/GGTalk/TalkBase.Client/Core/BaseGlobalCache.cs):

public void StartRefreshFriendInfo()
{
//直接使用线程,可以快速启动。
this.updateThread = (this.userManager.Count > 1) ? new Thread(new ParameterizedThreadStart(this.RefreshContactRTData)) : new Thread(new ParameterizedThreadStart(this.LoadContactsFromServer));
this.updateThread.Start();
}

当用户登录后窗体显示时,或断线重连成功时,此方法会被调用。这个方法的作用就是通过判断缓存中是否存在用户来决定刷新部分联系人数据还是重新从服务器加载数据。

如果缓存中只有自己一个人,表示是第一次在该电脑上登录,此时将执行LoadContactsFromServer方法以从服务器加载所有联系人等数据。

如果缓存中只有多个人,表示不是第一次在该电脑上登录,此时将执行RefreshContactRTData方法以更新本地数据到最新版本。

在这里我们主要需要关注RefreshContactRTData方法:

private void RefreshContactRTData(object state)
{
try
{
this.BatchLoadStarted();
ContactRTDatas contract = this.DoGetContactsRTDatas(); //1000用户数据量大小为22k
foreach (string userID in this.userManager.GetKeyList())
{
if (userID != this.currentUser.ID && !contract.UserStatusDictionary.ContainsKey(userID)) //最新的联系人中不包含缓存用户,则将之从缓存中删除。
{
this.userManager.Remove(userID);
if (this.FriendRemoved != null)
{
this.FriendRemoved(userID);
}
}
} foreach (KeyValuePair<string, UserRTData> pair in contract.UserStatusDictionary)
{
if (pair.Key == this.currentUser.ID) {
continue;
}
TUser origin = this.userManager.Get(pair.Key);
if (origin == null) //不存在于本地缓存中
{
TUser user = this.DoGetUser(pair.Key);
user.CommentName = this.currentUser.GetUnitCommentName(user.ID);
this.userManager.Add(user.ID, user);
if (this.UserBaseInfoChanged != null)
{
this.UserBaseInfoChanged(user);
}
}
else
{
//资料变化
if (pair.Value.Version != origin.Version) {
TUser user = this.DoGetUser(pair.Key);
user.CommentName = this.currentUser.GetUnitCommentName(user.ID);
user.LastWordsRecord = origin.LastWordsRecord;
user.ReplaceOldUnit(origin);
this.userManager.Add(user.ID, user);
if (this.UserBaseInfoChanged != null) {
this.UserBaseInfoChanged(user);
}
}
else {
//状态变化
if (origin.UserStatus != pair.Value.UserStatus) {
origin.UserStatus = pair.Value.UserStatus;
if (this.UserStatusChanged != null) {
this.UserStatusChanged(origin);
}
}
}
}
}

这个方法会先去获取当前登录用户在服务器上最新的联系人列表数据(仅仅包含ID、版本号、状态),然后去遍历缓存中用户的ID,检查来自服务器最新的联系人列表数据是否包含此ID对应的用户,若不包含则需要将此ID对应的用户从缓存中去除。接着再遍历来自服务器上最新联系人的用户数据,若此用户不存在于本地缓存,则下载该用户数据并将其加入缓存。接下来就是根据版本号比较来判断联系人的资料是否发生变化,若发生变化则将其同步到本地缓存。

四. 总结

GGTalk客户端缓存流程:在用户登录后,首先会从本地缓存文件中读取用户的好友列表、群组列表和快捷回复列表数据,将这些数据保存到内存中。然后,从服务器获取最新的联系人版本信息,与本地缓存比较后,下载需要更新的联系人资料。而当客户端窗口关闭,也就是退出登录时,会将该用户的好友列表、群组列表和快捷回复列表数据缓存到本地文件。

以上就是关于GGTalk客户端全局缓存设计的核心了,在接下来的一篇我们将介绍GGTalk中是如何收发消息及处理消息的。

敬请期待:《GGTalk 开源即时通讯系统源码剖析之:消息收发及处理》

GGTalk 开源即时通讯系统源码剖析之:客户端全局缓存及本地存储的更多相关文章

  1. GGTalk——C#开源即时通讯系统源码介绍系列(一)

    坦白讲,我们公司其实没啥技术实力,之所以还能不断接到各种项目,全凭我们老板神通广大!要知道他每次的饭局上可都是些什么人物! 但是项目接下一大把,就凭咱哥儿几个的水平,想要独立自主.保质保量保期地一个个 ...

  2. 新一代开源即时通讯应用源码定制 运营级IM聊天源码

    公司介绍:我们是专业的IM服务提供商!哇呼Chat是一款包含android客户端/ios客户端/pc客户端/WEB客户端的即时通讯系统.本系统完全自主研发,服务器端源码直接部署在客户主机.非任何第三方 ...

  3. 即时通信系统中实现全局系统通知,并与Web后台集成【附C#开源即时通讯系统(支持广域网)——QQ高仿版IM最新源码】

    像QQ这样的即时通信软件,时不时就会从桌面的右下角弹出一个小窗口,或是显示一个广告.或是一个新闻.或是一个公告等.在这里,我们将其统称为“全局系统通知”.很多使用C#开源即时通讯系统——GGTalk的 ...

  4. GGTalk ——C#开源即时通讯系统

    http://www.cnblogs.com/justnow/ GGTalk ——C#开源即时通讯系统 下载中心   GGTalk(简称GG)是可在广域网部署运行的QQ高仿版,2013.8.7发布GG ...

  5. 即时通信系统中实现聊天消息加密,让通信更安全【低调赠送:C#开源即时通讯系统(支持广域网)——GGTalk4.5 最新源码】

    在即时通讯系统(IM)中,加密重要的通信消息,是一个常见的需求.尤其在一些政府部门的即时通信软件中(如税务系统),对即时聊天消息进行加密是非常重要的一个功能,因为谈话中可能会涉及到机密的数据.我在最新 ...

  6. 可在广域网部署运行的即时通讯系统 -- GGTalk总览(附源码下载)

      (最新版本:V6.2,2019.01.03 .Xamarin移动端版本已经推出,包括 Android 和 iOS) GGTalk开源即时通讯系统(简称GG)是QQ的高仿版,同时支持局域网和广域网, ...

  7. 【转】可在广域网部署运行的即时通讯系统 -- GGTalk总览(附源码下载)

    原文地址:http://www.cnblogs.com/justnow/p/3382160.html (最新版本:V6.0,2017.12.11 .即将推出Xamarin移动端版本,包括 Androi ...

  8. GGTalk即时通讯系统(支持广域网)终于有移动端了!(技术原理、实现、源码)

    首先要感谢大家一直以来对于GGTalk即时通讯系统的关注和支持!GGTalk即时通讯系统的不断完善与大家的支持分不开! 从2013年最初的GG1.0开放源码以来,到后来陆续增加了网盘功能.远程协助功能 ...

  9. 【转】GGTalk即时通讯系统(支持广域网)终于有移动端了!(技术原理、实现、源码)

    原文地址:http://www.cnblogs.com/justnow/p/4836636.html 首先要感谢大家一直以来对于GGTalk即时通讯系统的关注和支持!GGTalk即时通讯系统的不断完善 ...

  10. 轻量级C#网络通信组件StriveEngine —— C/S通信开源demo(附源码)

    前段时间,有几个研究ESFramework网络通讯框架的朋友对我说,ESFramework有点庞大,对于他们目前的项目来说有点“杀鸡用牛刀”的意思,因为他们的项目不需要文件传送.不需要P2P.不存在好 ...

随机推荐

  1. 2022-06-01:给定一个数组arr,可能有正、有负、有0,无序。 只能挑选两个数字,想尽量让两个数字加起来的绝对值尽量小。 返回可能的最小的值。

    2022-06-01:给定一个数组arr,可能有正.有负.有0,无序. 只能挑选两个数字,想尽量让两个数字加起来的绝对值尽量小. 返回可能的最小的值. 答案2022-06-01: 排序,双指针. 代码 ...

  2. MMCM/PLL VCO

    输入输出时钟频率,input 322.265625Mhz, output 312.5Mhz 对于使用MMCM与PLL的不同情况,虽然输入输出频率是一样的,但是,分/倍频系数是不同的,不能使用同一套参数 ...

  3. Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535.

    问题描述 新建表或者修改表varchar字段长度的时候,出现这个错误 Row size too large. The maximum row size for the used table type, ...

  4. ET介绍——CSharp协程

    什么是协程 说到协程,我们先了解什么是异步,异步简单说来就是,我要发起一个调用,但是这个被调用方(可能是其它线程,也可能是IO)出结果需要一段时间,我不想让这个调用阻塞住调用方的整个线程,因此传给被调 ...

  5. Kubernetes GoRoutineMap工具包代码详解

    1.概述 GoRoutineMap 定义了一种类型,可以运行具有名称的 goroutine 并跟踪它们的状态.它防止创建具有相同名称的多个goroutine,并且在上一个具有该名称的 goroutin ...

  6. 荣登国家级榜单!ShowMeBug创始人李亚飞入选「科创中国·青年创业榜」

    近日,中国科协召开2022"科创中国"年度会议,会上发布了2021"科创中国"系列榜单.其中,ShowMeBug 创始人&CEO李亚飞入选2021年科创 ...

  7. 【一步步开发AI运动小程序】十、姿态动作相似度比较

    随着人工智能技术的不断发展,阿里体育等IT大厂,推出的"乐动力"."天天跳绳"AI运动APP,让云上运动会.线上运动会.健身打卡.AI体育指导等概念空前火热.那 ...

  8. RWKV – transformer 与 RNN 的强强联合

    在 NLP (Natural Language Processing, 自然语言处理) 领域,ChatGPT 和其他的聊天机器人应用引起了极大的关注.每个社区为构建自己的应用,也都在持续地寻求强大.可 ...

  9. Mdnice 简洁主题

    Md2All 是个人非常喜欢的一个 Markdown 排版工具,也是一直以来本公众号推文排版编辑的主要阵地.但发现 Md2All 有时候会非常卡(不太清楚是不是 Md2All 用了国外的服务器),而且 ...

  10. 用R语言实现并行计算:基于R的数据处理和分析工具

    目录 引言 随着数据量的爆炸式增长,数据处理和分析的需求也越来越大.传统的批处理计算已经无法满足高效的数据处理和分析需求,因此,并行计算成为了一个重要的技术方向.然而,R语言作为一种开源.可视化能力强 ...