IM系统-客户端架构(一)

即时通讯系统(Instant Messaging,简称IM),允许用户通过互联网实时相互发送/接收信息。在移动互联网时代,主流软件大都内置了自己的IM系统。除了微信、飞书这样的IM应用外,还有像淘宝、京东、美团也有自己的IM客服入口,小红书、抖音等社区视频类软件,也有自己的IM社交系统入口。

IM功能主要包括消息的收发、用户登录态管理、会话管理 等基础功能。还有群聊会话、富文本消息、语音视频聊天 等高级功能。当然对于飞书这样的办公应用,还有机器人、日历日程、开放平台、云文档 等定制化功能。同时当前AI兴起,在IM中也有大量应用场景,比如AI总结文档、AI总结未读消息等功能。

网络上有很多IM服务端相关文章,对于客户端的技术选型、细节等描述较少。本文主要聚焦在客户端架构选型上。

一、客户端整体架构

在一个公司中,可能会有多个业务方都用到了IM功能,为避免重复开发,一般会将IM业务封装为SDK,将通用业务下沉到SDK中,方便各个业务方直接使用。以字节跳动为例,正常的架构可能会如下:

IM服务端: 提供稳定、实时的各项服务,如消息收发、登录注册、消息同步、会话管理等功能。

客户端网络层SDK: 这里一般指长链SDK,即socket层。提供数据加解密、数据压缩、数据传输,保证数据实时、安全、高效传输。

客户端IM业务层SDK: 处理客户端的业务逻辑处理,如消息/会话同步、用户状态管理、未读数管理、数据存储、设置管理、本地缓存等能力。

二、网络选型

在IM系统中,网络层的选型无疑是最重要的。在实际使用中,通常会有这四种技术:短轮询长轮询WebSocketSSE

1. 短轮询

客户端定时,如每500毫秒向服务端发送HTTP请求,服务端处理完成后返回请求数据。HTTP Polling

优点: 实现简单、兼容性好

缺点: HTTP多次请求浪费客户端流量,占用多个服务端端口等资源。

实时性:

适用场景: 非常少,使用频率低的页面

在移动端的扫描二维码页面,会采用短轮询方式。还有网页端的赛事比分展示页面,也可能会采用短轮询方式。

短轮询开发成本极低,但是频繁调用服务端接口,对服务端的CPU和端口资源占用极大。在实际开发中使用较少。

2. 长轮询

客户端发送HTTP请求后,服务端挂起连接直到数据更新或超时,返回响应后,客户端立即发起新请求。

优点: 减少无效请求,比短轮询实时性好,资源消耗低

缺点: 服务端需维护挂起连接,高并发时资源消耗仍然比较大

实时性:

适用场景: 有实时性要求,但是无法使用WebSocketSSE的场景

在实际开发过程中,客户端还没有用到过这个技术。服务端的Nacos的配置中心使用较多,可查看文章:实现一个简单的长轮询

  • 首先客户端发起长轮询请求,服务端收到客户端的请求,这时会挂起客户端的请求,如果在服务端设计的30s之内都没有发生变更,服务端会响应回客户端数据没有变更,客户端会继续发送请求。

  • 如果在30s之内服务数据发生了变更,服务端会推送变更的数据到客户端。

3. Server Send Event (SSE)

Network Protocols behind Server Push, Online Gaming, and Emails

基于HTTP协议,客户端和服务端三次握手建立连接后,连接不断开,服务端可以主动推送数据流(如Content-Type: text/event-stream

优点: 基于HTTP,支持断线重连、内网穿透、心跳保活等功能

缺点: 半双工,仅支持服务端主动向客户端发送数据,不支持客户端向服务端发送数据。

实时性:

适用场景: 股票行情走势图、新闻推送

伴随AI发展,目前一些主流的AI应用,采用的也是SSE

在网页端问问题后,豆包和deepseek都有一个SSE连接,服务端不断向客户端返回问题结果,客户端根据服务端返回内容进行流式输出。

4. WebSocket

基于TCP的全双工协议,前期通过HTTP升级握手(Upgrade: websocket)建立持久连接,后续通过TCP实现双向实时通信。

优点: 全双工,客户端和服务端相互发送数据

缺点: 实现复杂,需要单独处理重连、保活。

实时性:

适用场景: 在线游戏、直播弹幕、Figma协同编辑

WebSocket在所有方案中,功能上是最强大的,但是实现成本较高。同时WebSocket只有一个连接通道,所以数据量很大的时候,也会有一定的延迟。

WebSocket建立连接的过程:

5. 总结

**** 短轮询 长轮询 SSE WebSocket
通信方向 客户端→服务器 客户端→服务器 服务器→客户端 双向通信
协议 HTTP HTTP HTTP WebSocket
实时性
资源消耗 高(频繁请求) 中(挂起连接) 低(长连接)

IM应用的数据量大、实时性要求高,短轮询长轮询显然不符合IM的要求。

对比SSEWebSocketSSE在开发成本上比WebSocket低,但是因为只支持单向通信,也不是WebSocket的最佳选择。而AI应用采用SSE的原因也很简单,因为从业务上看,AI应用的网络通信相对简单。在交互上,同一时间内一个会话只能发起一次请求,在SSE流式输出过程中,无法发起新的请求,或者可以发起新请求,老的请求会结束。

而IM应用中,在会话中,尤其是群聊会话中,在接受消息的同时,还可以多次发送消息,当然我们可以采用SSE+多个HTTP的框架,SSE负责服务端向客户端推送数据,HTTP负责客户端向服务端发送数据。但这样还是会有多个HTTP请求的资源浪费。

在IM应用中,使用WebSocket是最佳选择,虽然前期有一定开发成本,但是属于一劳永逸,后期复杂业务开发起来更加方便。

三、SDK选型

1. 长连SDK

长连SDK 目前推荐直接接入腾讯Mars,它更加贴合"移动互联网"、"移动平台"特性的网络解决方案,尤其针对弱网络、平台特性等有很多的相关优化策略。

微信终端跨平台组件 mars 系列(二) - 信令传输超时设计

mars/wiki

mars 是微信官方使用 C++ 编写的业务性无关、平台性无关的终端基础组件,目前在微信 AndroidiOSWindowsMacWindows Phone 等多个平台中使用,并正在筹备开源,它主要包含以下几个独立的部分:

  1. COMM:基础库,包括 socket、线程、消息队列、协程等基础工具;

  2. XLOG:通用日志模块,充分考虑移动终端的特点,提供高性能、高可用、安全性、容错性的日志功能;(详情点击:高性能日志模块xlog

  3. SDT:网络诊断模块;

  4. STN:信令传输网络模块,负责终端与服务器的小数据信令通道。包含了微信终端在移动网络上的大量优化经验与成果,经历了微信海量用户的考验。

2. 业务层SDK

业务层SDK主要包含数据存储、会话消息同步、登录管理等逻辑。

主要讨论点,在于使用C++层SDK还是各端平台SDK。这两种方式各有利弊,最终应该结合各团队的人员分布情况决定。

  • C++层SDK,即将业务下沉到C++层,各端通过接口调用,如AndroidJNI

  • 各端平台SDK,即各端将业务下沉到一个SDK中,可以快速复用,如AndroidaariOSframework等。

2.1 C++层SDK

优点
  1. C++层SDK比较通用,可以跨平台,一次开发多端使用,开发成本上会有一定优势。而且在聊天记录迁移等业务上,也有实用价值。

  2. C++层SDK性能更优,执行效率更高。

缺点
  1. 性能损耗

Android平台为例,纯JNI调用也会有额外耗时。

  • 执行环境的切换 :当从Java代码进入本地(C/C++)代码时,Java虚拟机(JVM)需要执行一系列操作来切换上下文。这包括保存当前Java线程的状态、切换到本地代码的执行环境,然后在返回时恢复状态。这个过程本身就有固定的时间成本。

  • 数据类型的转换与编组JavaC/C++使用不同的数据类型和内存模型。当参数和返回值在两者间传递时,JNI需要进行转换(编组)。例如,JavaString对象需要转换为C风格的 char*指针,数组也需要被JNI函数处理,这可能涉及整个数据块的复制。

  • JNI函数调用本身的开销 :在本地代码中,每次通过 JNIEnv*指针调用函数(如 GetFieldID, CallVoidMethod)都是一次函数调用。与虚拟机的交互使得JIT编译器难以对这些调用进行内联等优化。

  1. 线程切换

从开发规范上来看,所有的JNI调用都要在异步线程。这会导致一些简单的运算也要切换线程,导致渲染延迟。以会话ID为例,会话ID=会话id_会话类型。这是一个很简单的拼接逻辑,在某些场景下,我们客户端只有会话id,业务上需要获取对应的会话类型。那就必须调用C++的反解方法,根据拼接规则,反解获取会话类型。当然还有另一种方式,就是各端将反解逻辑再实现一遍,这就背离了通用SDK的初衷,而且规则是随业务发生变化的,后续会话id还可能拼接其他后缀,每一次改动都要各端适配。

会话id的例子是为了说明,即使是很简单的逻辑,通过JNI调用也有线程切换的损耗。

当然,上述问题也有对应的解决方案,那就是定义两类JNI方法。一种是异步线程调用的,参数可以有json格式数据,native可以有耗时操作。另一种是同步调用,参数只能有基本数据类型,native也不能有耗时操作。

  1. 内存增加

在部分业务上,为了达到快速访问效果,通常会做缓存。比如最近进入过的会话的消息,做消息缓存,下次进入可以快速显示,而不需要loadingC++层SDK一定会做消息的缓存。端上为了避免线程切换+JNI耗时又做一遍,会导致内存增加。

需要注意,以Android平台为例,虽然系统限制的是单个应用Java内存不能超过512M,但是对于应用占用的整体内存也有限制,不能超过1.2G。同时Android平台的LowMemoryKiller的清理规则中,会根据应用占用整体内存进行加权,整体内存占用多的更容易被LMK杀掉。所以不要忽略native内存带来的影响。

  1. 平台特性丢失
  • 在性能优化中,线程治理也是重要一环。当前的线程治理方案中,无论是字节码插桩还是其他方式,都无法做到对native线程的治理。

  • AndroidiOS平台的页面切换等生命周期,都需要通过JNI方法将事件通知给native

  1. 开发效率可能降低
  • 一般来说,C++层SDK都是有专门的团队的,那各平台与C++层SDK的沟通会有成本。

  • 在排查问题时,也需要客户端与C++层SDK的配合。

  • C++层SDK无法断点,一些问题定位只能通过日志排查。

2.2 各端平台SDK

优点

对比C++层的缺点,即为各端平台SDK的优点。

JNI调用损耗、简单逻辑运算无需切换线程、缓存逻辑不会重复占用内存、各种客户端性能优化方案都能生效、沟通效率高。

缺点
  1. 开发成本高,同一个功能,团队整体工时可能是C++层的3倍(Android/iOS/前端)

  2. 各端数据存储不一致,聊天记录迁移等功能可能无法实现

总结

具体选择哪种方式,应该根据各自业务团队风格选择。一般来说,业内的通用方案是采用C++层SDK的方案。因为人力成本>技术成本。

参考文章

菜鸟学网络之 ------ 长连接和短连接

转转客服IM聊天系统背后的技术挑战和实践分享-IM开发/专项技术区 - 即时通讯开发者社区!

探探的IM长连接技术实践:技术选型、架构设计、性能优化-IM开发/专项技术区 - 即时通讯开发者社区!

微信终端跨平台组件 mars 系列(二) - 信令传输超时设计

微信Mars策略分析

相关推荐
4z332 小时前
Android15 Framework(1): 用户空间启动的第一个进程 Init
android·源码阅读
洛克希德马丁3 小时前
Qt配置安卓开发环境
android·开发语言·qt
没有了遇见3 小时前
Android 修改项目包名,一键解决.
android
Entropless4 小时前
解剖OkHttp:那些主流教程未曾深入的设计精髓
android·okhttp
2501_915921434 小时前
查看iOS App实时日志的正确方式,多工具协同打造高效调试与问题定位体系(2025最新指南)
android·ios·小程序·https·uni-app·iphone·webview
菠萝加点糖4 小时前
Android 使用MediaMuxer+MediaCodec编码MP4视频异步方案
android·音视频·编码
cccccc语言我来了4 小时前
深入理解 Linux(7) 命令与动态库:从文件操作到程序链接的实践指南
android·linux·运维
程序员卷卷狗5 小时前
MySQL 慢查询优化:从定位、分析到索引调优的完整流程
android·mysql·adb
写点啥呢5 小时前
Android Studio 多语言助手插件:让多语言管理变得简单高效
android·ai·ai编程·多语言