从零开始开发IM(即时通讯)服务端(二)

IM(即时通讯)服务端(上) 前言 首先讲讲IM(即时通讯)技术可以用来做什么: 聊天:qq、微信 直播:斗鱼直播、抖音 实时位置共享、游戏多人互动等等 可以说几乎所有高实时性的应用场景都需要用到IM技术。 关于即时通讯架构的相关视频讲解:qq微信背后的即时通讯技术细节,方案选择 即时通讯架构设计 C/C++Linux后台服务器开发免费学习地址:C/C++Linux服务器开发高级架构师/Linux后台架构师 本篇将带大家从零开始搭建一个轻量级的IM服务端,麻雀虽小,五脏俱全,我们搭建的IM服务端实现以下功能: 一对 阅读详情

https://juejin.im/post/5d73af31f265da0394022e45

好消息:IM1.0.0 版本已经上线啦,支持特性:

  • 私聊发送文本 / 文件

  • 已发送 / 已送达 / 已读回执

  • 支持使用 ldap 登录

  • 支持接入外部的登录认证系统

  • 提供客户端 jar 包,方便客户端开发

github 链接: github.com/yuanrw/IM

本篇将带大家从零开始搭建一个轻量级的 IM 服务端,IM 的整体设计思路和架构在我的上篇博客中已经讲过了,没看过的同学请点击从零开始开发 IM(即时通讯)服务端 。

这篇将给大家带来更多的细节实现。我将从三个方面来阐述如何构建一个完整可靠的 IM 系统。

  1. 可靠性

  2. 安全性

  3. 存储设计

可靠性

什么是可靠性?对于一个 IM 系统来说,可靠的定义至少是不丢消息、消息不重复、不乱序,满足这三点,才能说有一个好的聊天体验。

不丢消息

我们先从不丢消息开始讲起。

首先复习一下上一篇设计的服务端架构:

我们先从一个简单例子开始思考:当 Alice 给 Bob 发送一条消息时,可能要经过这样一条链路:

  1. client-->connecter

  2. connector-->transfer

  3. transfer-->connector

  4. connector-->client

在这整个链路中的每个环节都有可能出问题,虽然 tcp 协议是可靠的,但是它只能保证链路层的可靠,无法保证应用层的可靠。

例如在第一步中, connector收到了从 client发出的消息,但是转发给 transfer失败,那么这条消息 Bob 就无法收到,而 Alice 也不会意识到消息发送失败了。

如果 Bob 状态是离线,那么消息链路就是:

  1. client-->connector

  2. connector-->transfer

  3. transfer-->mq

如果在第三步中, transfer收到了来自 connector的消息,但是离线消息入库失败, 那么这个消息也是传递失败了。
为了保证应用层的可靠,我们必须要有一个 ack 机制,使发送方能够确认对方收到了这条消息。

具体的实现,我们模仿 tcp 协议做一个应用层的 ack 机制。

tcp 的报文是以 字节(byte)为单位的,而我们以 message单位。

发送方每次发送一个消息,就要等待对方的 ack 回应,在 ack 确认消息中应该带有收到的 id 以便发送方识别。

其次,发送方需要维护一个等待 ack 的队列。 每次发送一个消息之后,就将消息和一个计时器入队。

另外存在一个线程一直轮询队列,如果有超时未收到 ack 的,就取出消息重发。

超时未收到 ack 的消息有两种处理方式:

  1. 和 tcp 一样不断发送直到收到 ack 为止。

  2. 设定一个最大重试次数,超过这个次数还没收到 ack,就使用失败机制处理,节约资源。例如如果是 connector长时间未收到 client的 ack,那么可以主动断开和客户端的连接,剩下未发送的消息就作为离线消息入库,客户端断连后尝试重连服务器即可。

不重复、不乱序

有的时候因为网络原因可能导致 ack 收到较慢,发送方就会重复发送,那么接收方必须有一个去重机制。
去重的方式是给每个消息增加一个唯一 id。这个唯一 id 并不一定是全局的,只需要在一个会话中唯一即可。例如某两个人的会话,或者某一个群。如果网络断连了,重新连接后,就是新的会话了,id 会重新从 0 开始。

接收方需要在当前会话中维护收到的最后一个消息的 id,叫做 lastId。
每次收到一个新消息, 就将 id 与 lastId作比较看是否连续,如果不连续,就放入一个暂存队列 queue 中稍后处理。

例如:

  • 当前会话的 lastId=1,接着服务器收到了消息 msg(id=2),可以判断收到的消息是连续的,就处理消息,将 lastId修改为 2。

  • 但是如果服务器收到消息 msg(id=3),就说明消息乱序到达了,那么就将这个消息入队,等待 lastId变为 2 后,(即服务器收到消息 msg(id=2)并处理完了),再取出这个消息处理。

因此,判断消息是否重复只需要判断 msgId>lastId&&!queue.contains(msgId)即可。如果收到重复的消息,可以判断是 ack 未送达,就再发送一次 ack。

接收方收到消息后完整的处理流程如下:

伪代码如下:

class ProcessMsgNode{
    /**
     * 接收到的消息
     */
    private Message message;
    /**
     * 处理消息的方法
     */
    private Consumer<Message> consumer;
}
public CompletableFuture<Void> offer(Long id,Message     message,Consumer<Message> consumer) {
    if (isRepeat(id)) {
    //消息重复
        sendAck(id);
        return null;
    }
    if (!isConsist(id)) {
    //消息不连续
        notConsistMsgMap.put(id, new ProcessMsgNode(message, consumer));
        return null;
    }
    //处理消息
    return process(id, message, consumer);
}
private CompletableFuture<Void> process(Long id, Message message, Consumer<Message> consumer) {
    return CompletableFuture
        .runAsync(() -> consumer.accept(message))
        .thenAccept(v -> sendAck(id))
        .thenAccept(v -> lastId.set(id))
        .thenComposeAsync(v -> {
            Long nextId = nextId(id);
            if (notConsistMsgMap.containsKey(nextId)) {
                //队列中有下个消息
                ProcessMsgNode node = notConsistMsgMap.get(nextId);
                return process(nextId, node.getMessage(), consumer);
            } else {
                //队列中没有下个消息
                CompletableFuture<Void> future = new CompletableFuture<>();
                future.complete(null);
                return future;
            }
        })
        .exceptionally(e -> {
            logger.error("[process received msg] has error", e);
            return null;
        });
}
复制代码

安全性

无论是聊天记录还是离线消息,肯定都会在服务端存储备份,那么消息的安全性,保护客户的隐私也至关重要。
因此所有的消息都必须要加密处理。
在存储模块里,维护用户信息和关系链有两张基础表,分别是 im_user用户表和 im_relation关系链表。

  • im_user表用于存放用户常规信息,例如用户名密码等,结构比较简单。

  • im_relation表用于记录好友关系,结构如下:

CREATE TABLE `im_relation` (
  `id` bigint(20) COMMENT '关系id',
  `user_id1` varchar(100) COMMENT '用户1id',
  `user_id2` varchar(100) COMMENT '用户2id',
  `encrypt_key` char(33) COMMENT 'aes密钥',
  `gmt_create` timestamp DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
  PRIMARY KEY (`id`),
  UNIQUE KEY `USERID1_USERID2` (`user_id1`,`user_id2`)
);
复制代码
  • user_id1和 user_id2是互为好友的用户 id,为了避免重复,存储时按照 user_id1< user_id2的顺序存,并且加上联合索引。

  • encrypt_key是随机生成的密钥。当客户端登录时,就会从数据库中获取该用户的所有的 relation,存在内存中,以便后续加密解密。

  • 当客户端给某个好友发送消息时,取出内存中该关系的密钥,加密后发送。同样,当收到一条消息时,取出相应的密钥解密。

客户端完整登录流程如下:

  1. client 调用 rest 接口登录。

  2. client 调用 rest 接口获取该用户所有 relation。

  3. client 向 connector 发送 greet 消息,通知上线。

  4. connector 拉取离线消息推送给 client。

  5. connector 更新用户 session。

那为什么 connector 要先推送离线消息再更新 session 呢?我们思考一下如果顺序倒过来会发生什么:

  1. 用户 Alice登录服务器

  2. connector更新 session

  3. 推送离线消息

  4. 此时 Bob 发送了一条消息给 Alice

如果离线消息还在推送的过程中,Bob 发送了新消息给 Alice,服务器获取到 Alice 的 session,就会立刻推送。这时新消息就有可能夹在一堆离线消息当中推过去了,那这时,Alice 收到的消息就乱序了。

而我们必须保证离线消息的顺序在新消息之前。

那么如果先推送离线消息,之后才更新 session。在离线消息推送的过程中,Alice 的状态就是 “未上线”,这时 Bob 新发送的消息只会入库 im_offline, im_offline表中的数据被读完之后才会 “上线” 开始接受新消息。这也就避免了乱序。

存储设计

存储离线消息

当用户不在线时,离线消息必然要存储在服务端,等待用户上线再推送。理解了上一个小节后,离线消息的存储就非常容易了。增加一张离线消息表 im_offline,表结构如下:

CREATE TABLE `im_offline` (
  `id` int(11) COMMENT '主键',
  `msg_id` bigint(20) COMMENT '消息id',
  `msg_type` int(2) COMMENT '消息类型',
  `content` varbinary(5000) COMMENT '消息内容',
  `to_user_id` varchar(100) COMMENT '收件人id',
  `has_read` tinyint(1) COMMENT '是否阅读',
  `gmt_create` timestamp COMMENT '创建时间',
  PRIMARY KEY (`id`)
);
复制代码

msg_type用于区分消息类型( chat, ack), content加密后的消息内容以 byte 数组的形式存储。
用户上线时按照条件 to_user_id=用户id拉取记录即可。

防止离线消息重复推送

我们思考一下多端登录的情况,Alice 有两台设备同时登陆,在这种并发的情况下,我们就需要某种机制来保证离线消息只被读取一次。

这里利用 CAS 机制来实现:

  1. 首先取出所有 has_read=false的字段。

  2. 检查每条消息的 has_read值是否为 false,如果是,则改为 true。这是原子操作。

update im_offline set has_read = true where id = ${msg_id} and has_read = false
复制代码
  1. 修改成功则推送,失败则不推送。

相信到这里,同学们已经可以自己动手搭建一个完整可用的 IM 服务端了。更多问题欢迎评论区留言~~

IM1.0.0 版本已上线,github 链接: github.com/yuanrw/IM
觉得对你有帮助请点个 star 吧~!

(完)

推荐阅读

Spring Boot整合JWT实现用户认证(附源码)

IDEA新特性:提前知道代码怎么走

还在担心写的一手烂SQL,送你4款工

瞬间几千次的重复提交,我用 SpringBoot+Redis 扛住了

爬了知乎“神回复”,笑得根本停不下来

这 17 个 JVM 参数,高级 Java 必须掌握!


好文!必须点赞
开发IM(即时通讯)服务端 首先讲讲IM(即时通讯)技术可以用来做什么:可以说几乎所有高实时性的应用场景都需要用到IM技术。 阅读详情

相关推荐

即时通讯源码/im源码uniapp基于在线聊天系统附完整搭建部署教程

搭建部署私信我。

百创科技 3611

10分钟学会写一款IM即时通讯安卓应用(包含前后端)

开发环境:Android Studio 前端语言:Java(基于开源的Smack库) 服务端语言:Java(基于开源的openfire)

大模型训练 1731

JAVA开发高性能IM即时通信系统源码.zip

JAVA开发高性能IM即时通信系统源码,JAVA语言开发的轻量、高性能、单机支持几十万至百万在线用户IM,主要目标降低即时通讯门槛,快速打造低成本接入在线IM系统,通过极简洁的消息格式就可以实现多端不同协议间的消息发送如内置(Http、Websocket、Tcp自定义IM协议) JAVA开发高性能IM即时通信系统源码,JAVA语言开发的轻量、高性能、单机支持几十万至百万在线用户IM,主要目标降低即时通讯门槛,快速打造低成本接入在线IM系统,通过极简洁的消息格式就可以实现多端不同协议间的消息发送如内置(Http、Websocket、Tcp自定义IM协议) JAVA开发高性能IM即时通信系统源码,JAVA语言开发的轻量、高性能、单机支持几十万至百万在线用户IM,主要目标降低即时通讯门槛,快速打造低成本接入在线IM系统,通过极简洁的消息格式就可以实现多端不同协议间的消息发送如内置(Http、Websocket、Tcp自定义IM协议) JAVA开发高性能IM即时通信系统源码,JAVA语言开发的轻量、高性能、单机支持几十万至百万在线用户IM,主要目标降低即时通讯门槛,快速打造低成本接入在线IM系

中通IM测试实践

消息的质量目标包括:准确性、时效性、可靠性、多端一致性、安全性、兼容性,这几个维度都是做好IM质量保障的重点及难点。基于业务特性分析,我们梳理出IM测试点,大致如下:(IM测试分析)IM测试,看似简单,其实个中门道很深。通过两年的中通自研IM平台测试实践,我们总结出以下经验:1.充分的用例场景是质量保障的基石。2.接口自动化是回归测试的利器。产品还在小步快跑,试错迭代,我们的测试活动并没有停止,同样我们的测试场景和接口脚本还在不停地增长,未来还需要进一步深度探索IM测试。

主要分享测试的学习资源,帮助快速了解测试行业,帮助想转行、进阶、小白成长为高级测试工程师。 3985

Ferry工单平台部署

ferry工单平台部署

运维求生之路 1897

陀螺仪及四元数解算

陀螺仪 四元数

zb313982521的博客 2006

2023 风车IM即时通讯源码+搭建教程

3.宝塔数据库添加数据库旁边有个root密码修改,然后导入数据库。nginx前端 后端配置文件 分别复制到站点配置文件里。ip:7000运行路径fcim/admin。前端http://你自己ip:7000。后端http://你自己ip:7001。后端文件server上传到opt目录。ip:7001运行路径fcim/h5。8.安卓用mt管理器修改ip以及图标。fcim上传到wwwroot下解压。配置文件里有两个地方填写自己的ip。ip:7000和ip:7001。7000(用作前端)7001(用作后端)

966

可信数据空间:可信空间、区域功能节点、业务功能节点、连接器的关联关系

可信数据空间通过分层架构实现安全数据共享:可信空间作为顶层制定全局规则;区域功能节点协调辖区内功能节点协作;功能节点提供具体数据服务;连接器作为唯一通信通道确保安全交互。系统采用"规则自上而下约束,数据自下而上流动"机制,通过分层治理和标准化连接,在保证数据主权的同时实现跨域协作,具有策略分级执行、去中心化治理等特性,支持灵活扩展业务场景。

友莘居士的博客 1690

视频教程-Android IM 即时通讯实战项目-Android

Android IM 即时通讯实战项目 从事多年Android软件开发及智能...

weixin_32175307的博客 823

Qt基础之十九:进程与QProcess

进程就是一个程序的执行实例,也就是正在运行的程序。对操作系统来说,进程就是一个担当分配系统资源CPU时间、内存的实体。在Qt 中,我们使用QProcess来表示一个进程。这个类可以允许我们的应用程序启动一个新的外部程序,并且与这个程序进行通讯。当外部程序开始启动时,QProcess的状态为Starting;外部程序已经启动时,QProcess的状态为Running,并发送started()信号;当外部层序退出了,QProcess的状态为NotRunning(初始状态),并发送finished()信号。

草上爬的博客 1万+

用 ABAP CDS 把树建起来:Hierarchy 的建模、调试与消费全流程

CDS Hierarchy 将树形数据结构作为一等公民引入 ABAP 开发,通过定义父子关联、根节点条件和兄弟排序规则,让数据库原生支持层级查询。相比传统递归处理方式,它能避免性能瓶颈,提供$node系统字段描述节点结构信息,并支持与SAP UI框架无缝集成。这种方法简化了组织架构、成本中心等业务场景的层级数据处理,使开发人员能够专注于业务逻辑而非底层实现。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 210

从零开始开发IM(即时通讯)服务端(二)附源码

本篇将带大家从零开始搭建一个轻量级的IM服务端,IM的整体设计思路和架构在我的上篇博客中已经讲过了,没看过的同学请点击从零开始开发IM(即时通讯)服务端。 这篇将给大家带来更多的细节实现。我将从三个方面来阐述如何构建一个完整可靠的IM系统。 可靠性 安全性 存储设计 可靠性 什么是可靠性?对于一个IM系统来说,可靠的定义至少是不丢消息、消息不重复、不乱序,满足这...

Java笔记虾 3657

构建.NET网站:VSCode还是Visual Studio?揭开开发工具的选择之谜

选择VSCode还是Visual Studio,归根结底是取决于你的具体需求和偏好。🔍 这两款工具各有千秋,都能为你的.NET网站开发提供强大的支持。希望这篇文章能够帮助你更全面地了解这两款工具,做出明智的选择。

墨夶的博客 789

从零开始开发IM(即时通讯)服务端(一)附源码

前言 首先讲讲IM(即时通讯)技术可以用来做什么: 聊天:qq、微信 直播:斗鱼直播、抖音 实时位置共享、游戏多人互动等等 可以说几乎所有高实时性的应用场景都需要用到IM技术。 本篇将带大家从零开始搭建一个轻量级的IM服务端,麻雀虽小,五脏俱全,我们搭建的IM服务端实现以下功能: 一对一的文本消息、文件消息通信 每个消息有“已发送”/“已送达”/“...

Java笔记虾 1万+

DICOM医学影像开发者必备工具清单

PyDicom 是一个纯 Python 实现的 DICOM 文件解析库,允许开发者读取、修改和写入 DICOM 文件。它无需依赖外部库,易于集成到现有的 Python 项目中,非常适合快速开发和原型设计。DCMTK(DICOM Toolkit)是一个由OFFIS开发的开源C++库,提供了一整套DICOM标准的实现,包括文件操作、网络通信和图像处理等功能。它是开发高性能DICOM应用的理想选择。MicroDICOM 是一款免费的DICOM查看器,适用于Windows平台。

编程技术探索者,分享C/C++、C#、Java、数据库等开发经验,聚焦实战技巧与AI兴趣,助力编程爱好者成长。 577

01 - 什么是SVM

摘要:SVM(共享虚拟内存)技术使CPU和GPU能共享同一虚拟地址空间,简化异构计算编程。传统模型需手动管理CPU/GPU内存和拷贝数据,而SVM允许直接传递指针,自动处理页面迁移和一致性维护。其核心包括统一地址空间、按需迁移、页面故障处理和一致性管理,显著降低编程复杂度并提升性能。通过HMM框架、MMU通知和GPU页面异常等机制实现高效内存共享,支持复杂数据结构,为异构计算带来便利。

图形/计算GPU的驱动与虚拟化技术分享园地 3137

从零开始开发IM(即时通讯)服务端

精选:★→2020年最新的常问企业面试题大全以及答案作者:yuanrw原文地址 https://juejin.im/post/5d6b3949f265da03c34c13e5好消息:I...

java思维导图 3728

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

本文由“yuanrw”分享,博客:juejin.im/user/5cefab8451882510eb758606,收录时内容有改动和修订。 0、引言 站长提示:本文适合IM新手阅读,但最好有一定的网络编程经验,必竟实践性的代码上手就是网络编程。如果你对网络编程,以及IM的一些理论知识知之甚少,请务必首先阅读:《新手入门一篇就够:从零开发移动端IM》,该文为IM小白分类整理了详尽的理论资料,请按...

hellojackjiang2011的博客 5275

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

从零开始开发IM(即时通讯)服务端附源码

首先讲讲IM(即时通讯)技术可以用来做什么:聊天:qq、微信直播:斗鱼直播、抖音实时位置共享、游戏多人互动等等可以说几乎所有高实时性的应用场景都需要用到IM技术。本篇将带大家从零开始搭建一个轻量级的IM服务端,麻雀虽小,五脏俱全,我们搭建的IM服务端实现以下功能:一对一的文本消息、文件消息通信每个消息有“已发送”/“已送达”/“已读”回执存储离线消息支持用户登录,好友关系等基本功能。能够方便地水平扩展。

开源直播系统源码 2274

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Android-IM从零开始开发一个即时通讯项目

Android-IM从零开始开发一个即时通讯项目 https://www.jianshu.com/p/dca480006691 关于聊天室项目 聊天室项目,也被称为即时通讯(IM)。 其原理是服务器是一直在启动状态的线程,不断的从客户端(App)获取消息,收到消息后,进行类型和发送目标判断,以发送到群组或者单聊的方式,客户端收到消息后进行界面的展示。 如果要自己开发即时通讯类的 App,那么必须得...

王人冉的博客 1278

安卓从零开始手把手教你开发一个带服务端的IM即时通讯安卓APP应用

安卓从零开始手把手教你开发一个带服务端的IM即时通讯安卓APP应用一、课程项目全体概要 二、推送与存储环境搭建三、项目前后端项目搭建与预备 四、客户端基本模块开发五、项目数据库模型规划六、用户注册登录与完善个人资料 七、好友管理功能开发八、客户端数据封装与入库九、单聊功用开发 十、群聊开发准备: 十一、消息的发送功能开发十二、表情发送功能开发 十三、发送图片功能开发 十四、语音聊天功能开发十五、项...

ou775968876的博客 2820
上一篇: 从零开始开发IM(即时通讯)服务端
下一篇: SpringBoot+JWT+Shiro+MybatisPlus实现Restful快速开发后端脚手架
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值