93、即时通讯:WebSocket使用、XMPP协议、MQTT协议、消息保活

即时通讯(IM)这个功能,几乎每个App都会用到。不管是聊天、推送,还是设备间的指令传输,背后都离不开一套可靠的通信协议。

我个人在早期做IM时,踩过不少坑。最惨的一次,线上用户反馈消息发不出去,结果发现是长连接断了,重连机制又没写好。嗯,从那以后,我对消息保活这件事就特别敏感。

今天这一讲,我们重点聊四个东西:WebSocketXMPPMQTT,以及消息保活。它们各有各的适用场景,选对了,事半功倍;选错了,后面全是坑。

核心观点:没有最好的协议,只有最合适的场景。WebSocket适合实时双向通信,XMPP适合标准化的聊天系统,MQTT适合物联网和低带宽场景。

一、WebSocket:最常用的实时通信方案

WebSocket是目前Android端最主流的实时通信方案。它基于TCP,建立一次连接后,客户端和服务端可以随时互发数据。

为什么它这么流行?说白了,HTTP是“请求-响应”模式,服务端没法主动给客户端发消息。而WebSocket是全双工的,服务端可以随时“推”数据过来。

1.1 基本使用

Android中使用WebSocket,我推荐用OkHttp自带的WebSocket支持。不需要额外引入第三方库,省心。

// 创建WebSocket客户端
OkHttpClient client = new OkHttpClient.Builder()
        .readTimeout(0, TimeUnit.MILLISECONDS)  // 长连接不能设超时
        .build();

Request request = new Request.Builder()
        .url("ws://your-server.com/chat")
        .build();

WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        // 连接建立成功
        webSocket.send("Hello Server!");
    }

    @Override
    public void onMessage(WebSocket webSocket, String text) {
        // 收到服务端消息
        Log.d("WS", "收到消息: " + text);
    }

    @Override
    public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        // 连接失败,需要重连
        reconnect();
    }
});

我的经验:readTimeout一定要设为0,否则连接空闲一段时间后会被自动关闭。我曾经因为这个坑,排查了整整一天。

1.2 心跳与重连

WebSocket本身没有保活机制。如果网络切换或者服务端重启,连接就会断开。所以,我们必须自己实现心跳和重连。

// 简单的心跳实现
Handler handler = new Handler();
Runnable heartbeatTask = new Runnable() {
    @Override
    public void run() {
        if (webSocket != null) {
            webSocket.send("PING");
            handler.postDelayed(this, 30_000); // 每30秒发一次
        }
    }
};

// 重连逻辑(指数退避)
int retryCount = 0;
void reconnect() {
    long delay = Math.min(1000 * (int) Math.pow(2, retryCount), 30_000);
    handler.postDelayed(() -> {
        retryCount++;
        connectWebSocket();
    }, delay);
}

注意:重连一定要用指数退避,不要一失败就立刻重连。否则服务端还没恢复,你的客户端已经把服务端打崩了。我见过有人每秒重连一次,结果服务端直接挂了。

二、XMPP协议:老牌聊天协议

XMPP(可扩展消息处理现场协议)是一种基于XML的即时通讯协议。它历史悠久,标准化程度高,很多早期IM系统(比如Google Talk)都用它。

说实话,现在纯用XMPP的App不多了。但如果你要做一套需要和第三方系统互通的标准聊天系统,XMPP仍然是个好选择。

2.1 XMPP的核心特点

  • 去中心化:任何人都可以运行自己的XMPP服务器,类似Email。
  • 扩展性强:基于XML,可以自定义扩展。
  • 标准成熟:有完整的RFC文档,Presence、Roster、Message都有明确定义。

2.2 Android上的XMPP库

Android上最常用的XMPP库是Smack。它功能完整,但有个问题——体积大,而且有些API比较老。

// Smack连接示例
AbstractXMPPConnection connection = new XMPPTCPConnection("example.com");
connection.connect();
connection.login("user", "password");

// 发送消息
Chat chat = ChatManager.getInstanceFor(connection)
        .createChat("friend@example.com", null);
chat.sendMessage("Hello!");

// 监听消息
ChatManagerListener listener = (chat, message) -> {
    Log.d("XMPP", "收到消息: " + message.getBody());
};
ChatManager.getInstanceFor(connection).addChatListener(listener);

我的建议:如果只是做App内部的聊天功能,别用XMPP。太重了。但如果你需要和外部系统(比如企业微信、钉钉)互通,XMPP的标准化优势就体现出来了。

三、MQTT协议:轻量级消息协议

MQTT(消息队列遥测传输)是IBM开发的轻量级发布/订阅协议。它设计目标就是低带宽、低功耗、高可靠。

你想想看,物联网设备、智能手表、车载系统,这些场景下MQTT几乎是首选。我最近做一个智能家居项目,就用MQTT来控制灯光和窗帘。

3.1 MQTT的核心概念

概念 说明
Broker 消息服务器,负责转发消息
Topic 消息主题,类似频道
Publisher 发布者,发送消息到某个Topic
Subscriber 订阅者,接收某个Topic的消息
QoS 服务质量等级(0、1、2)

3.2 Android集成MQTT

Android上最常用的MQTT库是Eclipse Paho。它稳定、成熟,而且支持Android Service模式。

// MQTT客户端示例
String broker = "tcp://broker.emqx.io:1883";
String clientId = MqttClient.generateClientId();

MqttAndroidClient client = new MqttAndroidClient(context, broker, clientId);

client.setCallback(new MqttCallback() {
    @Override
    public void connectionLost(Throwable cause) {
        // 连接断开,自动重连由MqttConnectOptions控制
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) {
        Log.d("MQTT", "收到消息: " + new String(message.getPayload()));
    }
});

MqttConnectOptions options = new MqttConnectOptions();
options.setAutomaticReconnect(true);  // 自动重连
options.setCleanSession(false);       // 保留会话状态
options.setConnectionTimeout(10);

client.connect(options);

我的经验:QoS等级要按需选择。QoS 0最快但可能丢消息,QoS 2最可靠但延迟高。聊天场景用QoS 1就够了,既保证不丢,又不会太慢。

四、消息保活:让连接永不掉线

消息保活是IM系统最头疼的问题。Android系统会杀进程、网络会切换、用户会锁屏……任何一个环节出问题,消息就收不到了。

我做过一个统计:没有保活机制的IM,消息到达率只有60%左右。加上保活后,能提升到95%以上。

4.1 保活的核心策略

  • 心跳机制:定期发送心跳包,检测连接是否存活。
  • 自动重连:断线后自动重连,使用指数退避。
  • 前台Service:将长连接放在前台Service中,降低被杀概率。
  • 系统广播唤醒:监听网络变化、开机广播,触发重连。
  • 多通道备份:WebSocket为主,同时保留FCM/华为推送作为备用通道。

4.2 前台Service保活示例

public class IMService extends Service {
    private static final int NOTIFICATION_ID = 1001;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 启动前台Service,降低被杀概率
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("消息服务运行中")
                .setSmallIcon(R.drawable.ic_chat)
                .build();
        startForeground(NOTIFICATION_ID, notification);

        // 初始化WebSocket连接
        initWebSocket();
        return START_STICKY;  // 被杀死后自动重启
    }
}

注意:START_STICKY不是万能的。Android 8.0以后,系统对后台Service限制非常严格。前台Service虽然能保活,但会显示一个通知,用户体验不好。需要权衡。

4.3 多通道备份策略

我现在的做法是:WebSocket为主通道,FCM/华为推送为备用通道

当WebSocket连接正常时,所有消息走WebSocket。一旦检测到WebSocket断开,立即通过推送通道发送一条“唤醒”消息,客户端收到后立刻重连WebSocket。

// 伪代码:多通道策略
if (webSocket.isConnected()) {
    // 主通道正常,直接发送
    webSocket.send(message);
} else {
    // 主通道断开,通过推送发送唤醒通知
    pushService.sendWakeUpMessage();
    // 同时启动重连
    reconnectWebSocket();
}

核心原则:不要把所有鸡蛋放在一个篮子里。主通道断了,备用通道要能顶上。哪怕备用通道只能发一条“快重连”的消息,也比完全失联强。

五、协议选型对比

最后,我整理了一张对比表,方便你根据场景选择。

协议 适用场景 优点 缺点
WebSocket App聊天、实时协作 全双工、延迟低、生态好 需要自己实现保活
XMPP 标准化聊天系统、企业IM 标准成熟、扩展性强 XML冗余、体积大
MQTT 物联网、低带宽场景 轻量、省电、QoS灵活 不适合复杂聊天逻辑

选型时,我建议你问自己三个问题:

  • 消息是双向还是单向?
  • 带宽和电量是否敏感?
  • 是否需要和外部系统互通?

想清楚这些,选协议就不难了。

即时通讯协议选型与保活策略 WebSocket XMPP MQTT 消息保活核心策略 • 心跳机制 • 自动重连(指数退避) • 前台Service • 系统广播唤醒 • 多通道备份 高可靠消息到达率 > 95%

好了,这一讲的内容就到这里。WebSocket、XMPP、MQTT各有千秋,消息保活是IM的基石。选对协议,做好保活,你的IM系统才能稳定运行。

公众号:蓝海资料掘金营,微信deep3321