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 开源即时通讯系统源码剖析之:聊天消息防错漏机制的更多相关文章
- GGTalk——C#开源即时通讯系统源码介绍系列(一)
坦白讲,我们公司其实没啥技术实力,之所以还能不断接到各种项目,全凭我们老板神通广大!要知道他每次的饭局上可都是些什么人物! 但是项目接下一大把,就凭咱哥儿几个的水平,想要独立自主.保质保量保期地一个个 ...
- 新一代开源即时通讯应用源码定制 运营级IM聊天源码
公司介绍:我们是专业的IM服务提供商!哇呼Chat是一款包含android客户端/ios客户端/pc客户端/WEB客户端的即时通讯系统.本系统完全自主研发,服务器端源码直接部署在客户主机.非任何第三方 ...
- 即时通信系统中实现全局系统通知,并与Web后台集成【附C#开源即时通讯系统(支持广域网)——QQ高仿版IM最新源码】
像QQ这样的即时通信软件,时不时就会从桌面的右下角弹出一个小窗口,或是显示一个广告.或是一个新闻.或是一个公告等.在这里,我们将其统称为“全局系统通知”.很多使用C#开源即时通讯系统——GGTalk的 ...
- GGTalk ——C#开源即时通讯系统
http://www.cnblogs.com/justnow/ GGTalk ——C#开源即时通讯系统 下载中心 GGTalk(简称GG)是可在广域网部署运行的QQ高仿版,2013.8.7发布GG ...
- Web API 源码剖析之默认消息处理程序链之路由分发器(HttpRoutingDispatcher)
Web API 源码剖析之默认消息处理程序链-->路由分发器(HttpRoutingDispatcher) 我们在上一节讲述了默认的DefaultServer(是一个类型为HttpServer的 ...
- 即时通信系统中实现聊天消息加密,让通信更安全【低调赠送:C#开源即时通讯系统(支持广域网)——GGTalk4.5 最新源码】
在即时通讯系统(IM)中,加密重要的通信消息,是一个常见的需求.尤其在一些政府部门的即时通信软件中(如税务系统),对即时聊天消息进行加密是非常重要的一个功能,因为谈话中可能会涉及到机密的数据.我在最新 ...
- Web API 源码剖析之默认消息处理程序链--》路由分发器(HttpRoutingDispatcher)
我们在上一节讲述了默认的DefaultServer(是一个类型为HttpServer的只读属性,详情请参考 Web API 源码剖析之全局配置).本节将讲述DefaultHandler(是一个Http ...
- Python 源码剖析(六)【内存管理机制】
六.内存管理机制 1.内存管理架构 2.小块空间的内存池 3.循环引用的垃圾收集 4.python中的垃圾收集 1.内存管理架构 Python内存管理机制有两套实现,由编译符号PYMALLOC_DEB ...
- thinkphp5源码剖析系列1-类的自动加载机制
前言 tp5想必大家都不陌生,但是大部分人都停留在应用的层面,我将开启系列随笔,深入剖析tp5源码,以供大家顺利进阶.本章将从类的自动加载讲起,自动加载是tp框架的灵魂所在,也是成熟php框架的必备功 ...
- SpringMVC源码剖析(五)-消息转换器HttpMessageConverter
原文链接:https://my.oschina.net/lichhao/blog/172562 #概述 在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分 ...
随机推荐
- 使用NSSM将.exe程序安装成windows服务
1.下载NSSM:NSSM - the Non-Sucking Service Manager 2.cmd方式安装服务 将下载的压缩包解压,找到nssm.exe,以管理员身份打开cmd,在cmd中定位 ...
- csapp-datalab(菜鸟小白版)
第1题: /* * bitXor - x^y using only ~ and & * Example: bitXor(4, 5) = 1 * Legal ops: ~ & * Max ...
- Cesium教程10-把影像和天空改成背景图片
在使用Cesium引擎时,我们经常要使用大屏适配导致地球或者模型的黑色天空盒和大屏的样式不匹配造成场景不好看的情况,这样就可以用到我们修改Cesium的天空为纯色背景,与大屏更适配,直接上代码. &l ...
- 关于armcc中static __inline修饰符的记录
相关的知识点:内联函数,static关键字 在一次stm32的库代码分析中发现static __inline 的函数定义,对此有些疑惑,static和inline两个关键字为什么要进行连用呢? 对此进 ...
- Java面试题:线程池内“闹情绪”的线程,怎么办?
在Java中,线程池中工作线程出现异常的时候,默认会把异常往外抛,同时这个工作线程会因为异常而销毁,我们需要自己去处理对应的异常,异常处理的方法有几种: 在传递的任务中去处理异常,对于每个提交到线程池 ...
- pageoffice 6 实现word文件添加水印
在很多场景下,Word文档正式发文之前,或者说形成最终文档之前,常常需要往Word文件中添加水印,并且会根据文件类型或内容的不同,需要添加的水印也不一样. 添加水印是Word软件里的一个简单功能,直接 ...
- Java学习之旅(day.09)
封装 把数据包装起来,给你能看的,不能看的包装起来不让你看 该露的露,该藏的藏 程序设计要求"高内聚,低耦合". 高内聚指类的内部数据操作细节自己完成,不允许外部干涉: 低耦合指仅 ...
- vue3.4中KeepAlive的一个bug
KeepAlive可以缓存组件,在不使用include时没有任何问题,可以正常缓存. 但是一旦使用了include,如果动态组件中没有导入ref函数,缓存功能就消失了 比如 editcom.vue & ...
- AI实战 | 手把手带你打造校园生活助手
大家好,我是努力的小雨.最近,我开始将我的经验逐渐分享到B站,希望能够帮助大家通过扣子助手真正解决一些问题.最近有粉丝提出了一个需求,为了满足大家的期待,我已经制作了视频,详细介绍了这个助手的搭建思路 ...
- 8.18考试总结(NOIP模拟43)[第一题·第二题·第三题·第四题]
愿你和重要的人,在来日重逢. 前言 题目名字起的很随意... 这天 Luogu 的运势好像是大凶(忌:打模拟赛,注意报零). 但是考得还不错,拿到了这么多场模拟赛以来第二三个场上AC. 所以说,我爱大 ...