IMLib 进阶:连接保活与重连

长连接是否稳定,决定了消息能不能实时到达。本页把连接参数(心跳、空闲超时、退避、最大尝试)、状态机、三端实现与常见故障放在一起,便于按参数排查“为什么总是断线/重连很慢/消息延迟”。

连接参数​

参数值含义调优建议
clientPingIntervalMs30000客户端主动 ping 间隔移动端后台可依赖 SDK 默认;不要自行缩短到 10s 以内(耗电与服务端压力)
serverIdleTimeoutMs90000服务端多久没收到任何帧判定空闲客户端 ping 间隔必须显著小于该值
pongRequiredtrue是否要求 pong未回 pong 时按 60000ms 后重连
baseMs1000重连退避基数指数退避起点
maxMs30000退避上限网络长期不可用时保持低频重试
maxAttempts8最大重连尝试次数达到后进入 SUSPEND(第 8 次后挂起)
sendRatePerMinute120单用户发送限频超限返回 1008
typingRatePerMinute60正在输入状态限频超限静默丢弃,不要重试

连接状态机​

状态触发业务含义客户端动作
CONNECTING调用 connect / 重连尝试正在握手(含 token 校验)展示“连接中”;可排队待发消息
CONNECTED收到服务端 hello可收发;离线消息随后补发刷新会话/未读;继续发送队列
SUSPEND异常断开后进入退避请求会失败(3001)暂停发送、提示网络异常、等待重连
DISCONNECT主动断开 / 鉴权失败 / 放弃重连长连接不可用清理定时器;需要时手动重连
KICKED同账号其它端登录触发踢下线当前端被挤下线提示并跳转登录(多端策略见下)

重连由 SDK 自动执行(指数退避 + jitter,1000ms 起、30000ms 上限、最多 8 次);"重连成功"的判定是收到 hello 且补发完成(sync_done)。业务不要在 CONNECTING/SUSPEND 状态下循环调用 connect,会与 SDK 的重连竞争。

三端实现​

import { createClient, Events } from '@ni-paas/im-web';

const client = createClient({ appkey: APPKEY, serverUrl: SERVER_URL });

client.addEventListener(Events.CONNECTING, (payload) => setStatus("连接中", payload));
client.addEventListener(Events.CONNECTED, (payload) => {
  setStatus("已连接", payload);
  flushSendQueue();          // 补发待发消息(clientMsgId 保证幂等)
  refreshConversations();     // 刷新会话与未读
});
client.addEventListener(Events.SUSPEND, (payload) => {
  setStatus("网络异常,自动重连中", payload);
  pauseSending();
});
client.addEventListener(Events.DISCONNECT, (payload) => {
  setStatus("连接断开", payload);
  // 1002 Token 失效:重新签发后 connect;不要循环重试旧 Token。
  if (payload.code === 1002) reissueTokenThenReconnect();
});
client.addEventListener(Events.KICKED, () => {
  showKickedDialog();        // 同账号在其他端登录
});

await client.connect(token);
client.addEventListener(IMClientEvent.CONNECTING, payload -> showConnecting(payload.getAttempt()));
client.addEventListener(IMClientEvent.CONNECTED, payload -> {
    showConnected();
    flushSendQueue();
});
client.addEventListener(IMClientEvent.SUSPEND, payload -> {
    showReconnecting();   // SDK 正在指数退避重连,不要重复 connect
});
client.addEventListener(IMClientEvent.DISCONNECT, payload -> {
    if (payload.getCode() == 1002) reissueTokenThenReconnect();
    else showDisconnected(payload.getReason());
});
client.addEventListener(IMClientEvent.KICKED, payload -> showKickedDialog());

client.connect(token, new ConnectCallback() {
    @Override public void onSuccess(String userId) { /* 已连接 */ }
    @Override public void onError(IMError error) {
        // 1002 Token / 3002 超时 / 3003 网络
        showConnectFailed(error.getCode(), error.getMessage());
    }
});
client.addEventListener(RongIMEvents.CONNECTING, (payload) => setState(() => status = "连接中"));
client.addEventListener(RongIMEvents.CONNECTED, (payload) async {
  setState(() => status = "已连接");
  await flushSendQueue();
});
client.addEventListener(RongIMEvents.SUSPEND, (payload) => setState(() => status = "自动重连中"));
client.addEventListener(RongIMEvents.DISCONNECT, (payload) {
  if (payload.code == 1002) reissueTokenThenReconnect();
});
client.addEventListener(RongIMEvents.KICKED, (payload) => showKickedDialog());

final res = await client.connect(token);
if (res.code != 0) showConnectFailed(res.code, res.msg);

多端在线与踢下线策略​

  • 同一账号可以多端同时在线(Web、手机、桌面各一条连接);消息会投递到该用户的全部在线会话,离线消息按设备补发。
  • KICKED 事件表示当前端被服务端要求下线(同端重复登录或管理端强制下线);收到后应停止重连并提示用户。
  • Electron 的 connect 支持 kickOtherDevice 选项:需要“新登录挤掉旧设备”时显式开启,否则默认允许多端。
  • 客户端不要用“重连”绕过踢下线:服务端会再次拒绝,形成无效循环。

弱网与后台切换​

场景表现建议
移动网络切换(4G↔Wi-Fi)连接断开后自动重连,期间请求 3003监听 CONNECTED 后刷新会话;发送队列保留 clientMsgId
App 退到后台(移动端)系统可能冻结进程,心跳停止回到前台时检查连接状态;必要时手动 connect
浏览器休眠/标签页冻结WS 被挂起,恢复后集中补发监听 visibilitychange,恢复时刷新会话与未读
代理/企业网关空闲断开固定时间无流量被断开保持 SDK 默认 ping;不要关闭心跳
服务端重启客户端进入 SUSPEND 并退避重连重连后按 seq 补拉(sync_done 后 refresh)

故障排查顺序​

  1. 看状态序列:是否反复 CONNECTING→SUSPEND(网络/服务端不可达)还是 CONNECTING→DISCONNECT(鉴权 1002)。
  2. 看错误码:1002 Token;3001 未就绪;3002 超时;3003 网络;0.9995004995004995 服务端/依赖。
  3. 看时间:重连耗时是否符合退避公式(1s、2s、4s…上限 30s);若每次都是 1s 说明连接立即失败(非退避问题)。
  4. 看服务端:连接明细(管理端)里的最后心跳时间、会话数;服务端空闲超时为 90000ms。
  5. 看客户端:是否自己定时调用了 disconnect、是否在 SUSPEND 期间重复 connect、是否被多个页面重复创建客户端实例。

相关链接​