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

IM功能主要包括消息的收发、用户登录态管理、会话管理 等基础功能。还有群聊会话、富文本消息、语音视频聊天 等高级功能。当然对于飞书这样的办公应用,还有机器人、日历日程、开放平台、云文档 等定制化功能。同时当前AI兴起,在IM中也有大量应用场景,比如AI总结文档、AI总结未读消息等功能。
网络上有很多IM服务端相关文章,对于客户端的技术选型、细节等描述较少。本文主要聚焦在客户端架构选型上。
一、客户端整体架构
在一个公司中,可能会有多个业务方都用到了IM功能,为避免重复开发,一般会将IM业务封装为SDK,将通用业务下沉到SDK中,方便各个业务方直接使用。以字节跳动为例,正常的架构可能会如下:

IM服务端: 提供稳定、实时的各项服务,如消息收发、登录注册、消息同步、会话管理等功能。
客户端网络层SDK: 这里一般指长链SDK,即socket层。提供数据加解密、数据压缩、数据传输,保证数据实时、安全、高效传输。
客户端IM业务层SDK: 处理客户端的业务逻辑处理,如消息/会话同步、用户状态管理、未读数管理、数据存储、设置管理、本地缓存等能力。
二、网络选型
在IM系统中,网络层的选型无疑是最重要的。在实际使用中,通常会有这四种技术:短轮询、长轮询、WebSocket、SSE。
1. 短轮询
客户端定时,如每500毫秒向服务端发送HTTP请求,服务端处理完成后返回请求数据。HTTP Polling

优点: 实现简单、兼容性好
缺点: HTTP多次请求浪费客户端流量,占用多个服务端端口等资源。
实时性: 差
适用场景: 非常少,使用频率低的页面
在移动端的扫描二维码页面,会采用短轮询方式。还有网页端的赛事比分展示页面,也可能会采用短轮询方式。

短轮询开发成本极低,但是频繁调用服务端接口,对服务端的CPU和端口资源占用极大。在实际开发中使用较少。
2. 长轮询
客户端发送HTTP请求后,服务端挂起连接直到数据更新或超时,返回响应后,客户端立即发起新请求。

优点: 减少无效请求,比短轮询实时性好,资源消耗低
缺点: 服务端需维护挂起连接,高并发时资源消耗仍然比较大
实时性: 好
适用场景: 有实时性要求,但是无法使用WebSocket、SSE的场景
在实际开发过程中,客户端还没有用到过这个技术。服务端的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的要求。
对比SSE和WebSocket,SSE在开发成本上比WebSocket低,但是因为只支持单向通信,也不是WebSocket的最佳选择。而AI应用采用SSE的原因也很简单,因为从业务上看,AI应用的网络通信相对简单。在交互上,同一时间内一个会话只能发起一次请求,在SSE流式输出过程中,无法发起新的请求,或者可以发起新请求,老的请求会结束。
而IM应用中,在会话中,尤其是群聊会话中,在接受消息的同时,还可以多次发送消息,当然我们可以采用SSE+多个HTTP的框架,SSE负责服务端向客户端推送数据,HTTP负责客户端向服务端发送数据。但这样还是会有多个HTTP请求的资源浪费。
在IM应用中,使用WebSocket是最佳选择,虽然前期有一定开发成本,但是属于一劳永逸,后期复杂业务开发起来更加方便。
三、SDK选型
1. 长连SDK
长连SDK 目前推荐直接接入腾讯Mars,它更加贴合"移动互联网"、"移动平台"特性的网络解决方案,尤其针对弱网络、平台特性等有很多的相关优化策略。
微信终端跨平台组件 mars 系列(二) - 信令传输超时设计
mars 是微信官方使用 C++ 编写的业务性无关、平台性无关的终端基础组件,目前在微信 Android、iOS、Windows、Mac、Windows Phone 等多个平台中使用,并正在筹备开源,它主要包含以下几个独立的部分:
-
COMM:基础库,包括socket、线程、消息队列、协程等基础工具; -
XLOG:通用日志模块,充分考虑移动终端的特点,提供高性能、高可用、安全性、容错性的日志功能;(详情点击:高性能日志模块xlog ) -
SDT:网络诊断模块; -
STN:信令传输网络模块,负责终端与服务器的小数据信令通道。包含了微信终端在移动网络上的大量优化经验与成果,经历了微信海量用户的考验。
2. 业务层SDK
业务层SDK主要包含数据存储、会话消息同步、登录管理等逻辑。
主要讨论点,在于使用C++层SDK还是各端平台SDK。这两种方式各有利弊,最终应该结合各团队的人员分布情况决定。
-
C++层SDK,即将业务下沉到C++层,各端通过接口调用,如Android的JNI等 -
各端平台SDK,即各端将业务下沉到一个SDK中,可以快速复用,如
Android的aar,iOS的framework等。
2.1 C++层SDK
优点
-
C++层SDK比较通用,可以跨平台,一次开发多端使用,开发成本上会有一定优势。而且在聊天记录迁移等业务上,也有实用价值。 -
C++层SDK性能更优,执行效率更高。
缺点
- 性能损耗
以Android平台为例,纯JNI调用也会有额外耗时。
-
执行环境的切换 :当从
Java代码进入本地(C/C++)代码时,Java虚拟机(JVM)需要执行一系列操作来切换上下文。这包括保存当前Java线程的状态、切换到本地代码的执行环境,然后在返回时恢复状态。这个过程本身就有固定的时间成本。 -
数据类型的转换与编组 :
Java和C/C++使用不同的数据类型和内存模型。当参数和返回值在两者间传递时,JNI需要进行转换(编组)。例如,Java的String对象需要转换为C风格的char*指针,数组也需要被JNI函数处理,这可能涉及整个数据块的复制。 -
JNI函数调用本身的开销 :在本地代码中,每次通过
JNIEnv*指针调用函数(如GetFieldID,CallVoidMethod)都是一次函数调用。与虚拟机的交互使得JIT编译器难以对这些调用进行内联等优化。
- 线程切换
从开发规范上来看,所有的JNI调用都要在异步线程。这会导致一些简单的运算也要切换线程,导致渲染延迟。以会话ID为例,会话ID=会话id_会话类型。这是一个很简单的拼接逻辑,在某些场景下,我们客户端只有会话id,业务上需要获取对应的会话类型。那就必须调用C++的反解方法,根据拼接规则,反解获取会话类型。当然还有另一种方式,就是各端将反解逻辑再实现一遍,这就背离了通用SDK的初衷,而且规则是随业务发生变化的,后续会话id还可能拼接其他后缀,每一次改动都要各端适配。
会话id的例子是为了说明,即使是很简单的逻辑,通过JNI调用也有线程切换的损耗。
当然,上述问题也有对应的解决方案,那就是定义两类JNI方法。一种是异步线程调用的,参数可以有json格式数据,native可以有耗时操作。另一种是同步调用,参数只能有基本数据类型,native也不能有耗时操作。
- 内存增加
在部分业务上,为了达到快速访问效果,通常会做缓存。比如最近进入过的会话的消息,做消息缓存,下次进入可以快速显示,而不需要loading。C++层SDK一定会做消息的缓存。端上为了避免线程切换+JNI耗时又做一遍,会导致内存增加。
需要注意,以Android平台为例,虽然系统限制的是单个应用Java内存不能超过512M,但是对于应用占用的整体内存也有限制,不能超过1.2G。同时Android平台的LowMemoryKiller的清理规则中,会根据应用占用整体内存进行加权,整体内存占用多的更容易被LMK杀掉。所以不要忽略native内存带来的影响。
- 平台特性丢失
-
在性能优化中,线程治理也是重要一环。当前的线程治理方案中,无论是字节码插桩还是其他方式,都无法做到对
native线程的治理。 -
Android和iOS平台的页面切换等生命周期,都需要通过JNI方法将事件通知给native。
- 开发效率可能降低
-
一般来说,
C++层SDK都是有专门的团队的,那各平台与C++层SDK的沟通会有成本。 -
在排查问题时,也需要客户端与
C++层SDK的配合。 -
且
C++层SDK无法断点,一些问题定位只能通过日志排查。
2.2 各端平台SDK
优点
对比C++层的缺点,即为各端平台SDK的优点。
无JNI调用损耗、简单逻辑运算无需切换线程、缓存逻辑不会重复占用内存、各种客户端性能优化方案都能生效、沟通效率高。
缺点
-
开发成本高,同一个功能,团队整体工时可能是
C++层的3倍(Android/iOS/前端) -
各端数据存储不一致,聊天记录迁移等功能可能无法实现
总结
具体选择哪种方式,应该根据各自业务团队风格选择。一般来说,业内的通用方案是采用C++层SDK的方案。因为人力成本>技术成本。
参考文章
转转客服IM聊天系统背后的技术挑战和实践分享-IM开发/专项技术区 - 即时通讯开发者社区!
探探的IM长连接技术实践:技术选型、架构设计、性能优化-IM开发/专项技术区 - 即时通讯开发者社区!