93、即时通讯:WebSocket使用、XMPP协议、MQTT协议、消息保活
即时通讯(IM)这个功能,几乎每个App都会用到。不管是聊天、推送,还是设备间的指令传输,背后都离不开一套可靠的通信协议。
我个人在早期做IM时,踩过不少坑。最惨的一次,线上用户反馈消息发不出去,结果发现是长连接断了,重连机制又没写好。嗯,从那以后,我对消息保活这件事就特别敏感。
今天这一讲,我们重点聊四个东西:WebSocket、XMPP、MQTT,以及消息保活。它们各有各的适用场景,选对了,事半功倍;选错了,后面全是坑。
核心观点:没有最好的协议,只有最合适的场景。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各有千秋,消息保活是IM的基石。选对协议,做好保活,你的IM系统才能稳定运行。