1. 为什么小程序里要单独聊WebSocket
这些年做微信小程序开发,最绕不开的一个痛点就是实时通信。聊天界面要刷出新消息、订单状态要立刻变化、游戏对战得同步位置,如果全靠HTTP轮询去定时拉取,性能和体验都撑不住。微信小程序原生的WebSocket能力,就是为这类场景准备的。
WebSocket在小程序里并不仅仅是一套API,它背后牵扯到连接管理、心跳保活、断线重连、消息时序、页面生命周期协同等一系列工程问题。很多开发者第一次接入时,按文档写完能通,但一跑到真机上就出现消息延迟、几十秒后自动断开、回调串台等莫名其妙的问题。这篇文章就围绕小程序里的WebSocket,把从API用法到工程落地的完整链路梳理一遍,顺便把我在实际项目里踩过的坑和验证过的做法放出来,给需要做实时功能的同学一个可以直接抄的参考。
适合来看这篇内容的,包括刚开始接触小程序开发、正在给项目接入WebSocket的初级工程师,也包括已经在用但经常被连接稳定性问题困扰、想系统化排查的中级开发者。我会尽量把每一个关键步骤都展开来讲,并且给出可运行的代码片段。
有一点先说明白:小程序不像浏览器环境那么自由,原生提供的WebSocket能力虽然有,但API风格和H5有些差异,同时又受制于微信的框架、生命周期和审核规则,所以很多习惯做前端的同学需要稍微调整一下思路。
1.1 小程序实时通信的技术选型逻辑
先回答一个问题:小程序里做实时消息,为什么首选WebSocket?
正常思维下,最传统的方案是HTTP短轮询,定时器隔几秒请求一次接口,把最新数据拉回来。短轮询的问题在于:请求频繁,服务器压力大;延迟不可控,用户看到的消息永远比实际慢几秒;大量无效请求浪费流量,尤其在弱网环境下体验非常糟。我见过一个商城小程序项目,为了等支付回调结果,前端每3秒就去轮询一次订单状态,高峰期服务器光处理轮询请求就占了将近一半的QPS,后来全部改成了WebSocket推送,才把资源省出来。
还有一种做法是长轮询,客户端发起请求后服务端保持连接不返回,直到有新数据才响应,客户端收到后立即再发起下一次请求。长轮询比短轮询聪明一点,但依旧是一次请求一次响应,频繁建立HTTP连接的开销没有消除,而且容易在代理层超时被切断。
WebSocket的本质是TCP连接之上的全双工通信通道,一次握手建立连接,之后双方都能随时互发数据,不需要像HTTP那样反复握手。在小程序里,WebSocket的API就是围绕这个模型设计的:
wx.connectSocket负责建立连接wx.onSocketMessage接收数据wx.sendSocketMessage发送数据wx.onSocketClose监听关闭wx.onSocketError监听错误
这一套能力组合起来,就足够覆盖绝大多数实时业务了。而且小程序官方提供的WebSocket API同时支持文本和二进制数据,对JSON数据交换、消息推送、聊天室、直播弹幕、协同编辑、设备状态上报等场景都比较友好。
1.2 WebSocket与传统HTTP在小程序中的差异体验
这里说一个很直观的对比:HTTP请求里,每次你都带着一个明确的请求发起动作,比如点击按钮、下拉刷新,它是典型的"请求-响应"模式。而WebSocket的连接是常驻的,数据是随时可能从服务端主动推过来的,这就意味着你的代码里必须有"被动接收、主动消化"的逻辑,而不是像写普通接口那样“发了请求等着回调”。
在小程序场景里,这个差异会被放大,因为微信小程序的页面存活机制比较复杂。页面切到后台、用户接了电话、切走又切回,这些场景都可能影响WebSocket连接的表现。如果代码里没有处理好连接的生命周期管理,很常见的现象就是:用户切出去5分钟再回来,消息一条都收不到了,但界面上还显示着"已连接"。
另外还要注意一个细节,微信开发者工具里WebSocket的表现和真机表现并不完全一致。开发者工具的网络环境通常比较宽松,不容易断连,而真机在弱网、切换Wi-Fi、锁屏等场景下很容易触发系统级别的网络重置,进而导致WebSocket被静默断开。这也是为什么很多项目“工具里测得好好的,一上真机就断”,根本原因不在你的代码,而在连接保活机制设计得不够健壮。
1.3 开发者容易忽略的API细节
不绕弯子,直接说几个我用下来容易踩的角色细节:
第一,wx.connectSocket的url参数必须是以wss://或ws://开头的合法地址。生产环境必须使用wss://,否则在真机上有大概率连接失败,微信官方对明文ws的限制越来越严格,开发阶段可以通过开发者工具的“不校验合法域名”临时调试,但上线审核时如果连的是非wss地址,直接会被拦下来。
第二,同一个页面里多次调用wx.connectSocket时需要注意,如果上一次连接还没有关闭,新连接发起后会先触发旧连接的关闭回调。所以如果你做了重连逻辑,但没管理好旧连接句柄,就会出现两个连接纠缠在一起、消息重复收到的问题。
第三,wx.sendSocketMessage的发送时机很有讲究。连接建立是异步的,必须等wx.onSocketOpen回调触发之后才能发送。很多新手栽在初始化顺序上,在调用connectSocket之后立刻send,结果消息根本没发出去,控制台也不报错。
2. 核心细节:连接建立与心跳保活机制
2.1 连接建立流程的前前后后
先画一个完整的时间线,帮你在脑子里建好模型:客户端发起wx.connectSocket→ 触发onSocketOpen→ 双方握手完成 → 可以自由收发消息。看起来很简单,但这里面有几个节点值得展开讲。
握手完成的标志以wx.onSocketOpen为准,不要试图用其他方式猜测连接状态。进入这个回调之后,理论上就可以发消息了。我在项目里一般会在onSocketOpen内打印一条包含时间戳的日志,用肉眼确认连接的建立速度,这个习惯在排查弱网问题时会派上用场,因为连接建立时间本身就是判断网络质量的一个参考指标。
连接失败也是要重点处理的场景。wx.connectSocket失败时并不一定会触发onSocketError,至少在我的实测里,某些异常情况下(比如域名解析失败)会直接不回调,表现就是连接“卡住”了,状态一直停在连接中。所以我建议在封装模块时,给连接过程设置一个超时定时器,比如5秒内没有收到onSocketOpen就强制关闭并触发重连。这个逻辑听起来简单,但很多团队确实没做,才导致线上出现“连接永远建立不起来但也不报错”的僵尸状态。
另一个容易被忽略的点是header参数。wx.connectSocket支持传入自定义header,这在做用户鉴权时非常有用。常规做法是把token放在header里,服务端在握手阶段校验身份,校验失败直接拒绝连接。但要注意,某些服务端框架对header里的中文或特殊符号处理不够友好,建议token统一用纯ASCII字符串,避免编码问题。
2.2 心跳机制:连接保活的核心手段
心跳机制是WebSocket开发里绕不过去的核心问题。为什么需要心跳?因为WebSocket连接虽然通常设计为长连接,但网络链路中的路由器、运营商NAT网关、服务器的空闲连接回收策略,都会对长时间没有数据传输的连接做清理。表现到业务上就是:连接看似还在,实际上服务端或网络中间层早就把连接断了,只是两端没有感知而已。
心跳就是用来对抗这种“沉默断线”的。最简单的心跳交互是:客户端定时向服务端发送一个轻量的ping消息,服务端收到后回复一个pong,如果客户端连续几次没收到pong,就判定连接已不可用,主动断开并重连。
我在实际项目中用过两种心跳实现,各有优劣:
| 心跳方式 | 实现思路 | 优点 | 缺点 |
|---|---|---|---|
| 客户端定时ping | 小程序每隔N秒发送一条心跳消息 | 实现简单,主动权在客户端 | 需要服务端配合应答,服务端不做处理就失去意义 |
| 双向定时校验 | 客户端ping,同时记录服务端最后一次推送数据的时间,服务端超过阈值没主动推数据则客户端也发心跳探测 | 更稳健,能识别“服务端还活着但连接断了”的情况 | 逻辑复杂度高一些 |
实际项目里我推荐用第一种,因为大多数业务的服务端是自建的,配合做pong响应成本很低。心跳间隔的选择也有讲究,太短了浪费流量资源,太长了起不到保活效果。我一般把心跳间隔设置在25秒到30秒之间,连续两次无响应才触发重连,这样既能有效保活,又不会因为单次网络抖动误判。
心跳消息体本身要尽量精简,比如定义协议时约定一个固定的特殊指令,像{"type":"ping"},收到{"type":"pong"}即认为连接健康。如果服务端有现成的消息推送框架,也可以复用它的心跳机制,不一定自己造轮子。
2.3 断线重连:策略与避坑
断线重连是稳定性的最后一道防线,但也是踩坑重灾区。无脑重连是个大忌。我就见过有项目在onSocketClose里直接connectSocket,结果一旦服务端短暂不可用,就会进入"连不上→关闭→立刻重连"的死循环,短时间内发起几百次连接请求,不只是耗电,可能直接触发微信端的API频率限制。
更合理的做法是:给重连增加退避机制。第一次断线后延迟2秒重连,第二次4秒,第三次8秒,以此类推,最大延迟时间封顶在30秒或1分钟。每次重连成功之后重置这个延迟倍数。这种指数退避策略能有效避免突刺式请求,也能给服务端留出恢复时间。
重连还要注意业务状态的一致性。断线期间用户发的消息,如果只是简单丢弃,重连后用户以为自己发出去了,实际服务端根本没收到,这就是消息丢失。稍微好一点的做法是在发送时给每条消息生成一个自增序号,缓存到本地,重连成功后把未确认的消息重新补发一遍。这个就涉及到“消息可靠性”的话题了,建议根据业务场景决定要不要做,如果只是接收展示类的推送,不做也可以;如果是聊天、指令下发类,最好做一下。
有一个隐藏很深的坑是:页面已经销毁或者切到了后台,此时WebSocket连接是否还要保留。我的建议是小程序全局的连接对象和页面的关系要解耦,不要把连接实例挂在页面的data里。连接对象放在App实例或全局单例模块里维护,页面只负责订阅消息和展示,这样页面切换不会影响连接本身的存续。
3. 实操:封装一个可靠的小程序WebSocket模块
3.1 连接管理器设计思路
直接给一套我项目里验证过的基本代码骨架。这套骨架的核心是一个单例管理类,职责划分比较清晰:负责建立连接、发送消息、统一处理回调、维护状态机、提供页面订阅接口。
先定义一个简单的状态机,字段用数字区分,方便日志打点:
const SocketState = { IDLE: 0, // 空闲,未连接 CONNECTING: 1, // 连接中 OPEN: 2, // 已连接 CLOSING: 3, // 关闭中 CLOSED: 4 // 已关闭 };连接管理器至少需要维护以下几个内部属性:
socketTask:wx.connectSocket返回的实例,后续send、close都调用它state:当前连接状态heartbeatTimer:心跳定时器句柄reconnectTimer:重连定时器句柄reconnectCount:连续重连次数,用于计算退避延迟listeners:业务方注册的回调池
class SocketManager { constructor() { this.socketTask = null; this.state = SocketState.IDLE; this.heartbeatTimer = null; this.reconnectTimer = null; this.reconnectCount = 0; this.listeners = {}; } connect(url, options = {}) { if (this.state === SocketState.OPEN) return; if (this.state === SocketState.CONNECTING) return; this.state = SocketState.CONNECTING; this.socketTask = wx.connectSocket({ url: url, header: options.header || {}, success: () => { console.log('[SocketManager] connectSocket called'); }, fail: (err) => { console.warn('[SocketManager] connectSocket fail', err); this.state = SocketState.IDLE; this.scheduleReconnect(url, options); } }); this.socketTask.onOpen(() => { console.log('[SocketManager] socket opened'); this.state = SocketState.OPEN; this.reconnectCount = 0; this.startHeartbeat(); this.emit('open'); }); this.socketTask.onMessage((res) => { let data = res.data; // 如果服务端返回的是字符串,尝试转成JSON,方便业务方统一处理 if (typeof data === 'string') { try { data = JSON.parse(data); } catch (e) {} } this.emit('message', data); }); this.socketTask.onError((err) => { console.warn('[SocketManager] socket error', err); this.emit('error', err); }); this.socketTask.onClose(() => { console.warn('[SocketManager] socket closed'); this.stopHeartbeat(); // 只有预期内的关闭才不重连 if (this.state !== SocketState.CLOSING) { this.state = SocketState.CLOSED; this.scheduleReconnect(url, options); } this.emit('close'); }); } }这里重点说明一下onClose里的判断逻辑。如果是业务方主动调用close()来关闭连接,那么把state先置为CLOSING,这样在onClose里就不会走重连分支。否则无论是服务端断开、网络切换导致的关闭,都会无条件进入重连流程。
3.2 心跳与重连的代码落地
心跳的启动可以放在onOpen里,用setInterval维护一个定时器。注意定时器要在模块级变量里保持引用,避免被GC回收。
startHeartbeat() { if (this.heartbeatTimer) return; this.heartbeatTimer = setInterval(() => { if (this.state !== SocketState.OPEN) return; // 发送ping消息,同时记录发送时间 this.sendRaw({ type: 'ping' }); this.lastPingTime = Date.now(); }, 25000); } stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } }重连调度用递归加定时器的方式,每次重连失败或者连接断开后都调用scheduleReconnect:
scheduleReconnect(url, options) { if (this.reconnectTimer) return; const delay = Math.min(30000, 2000 * Math.pow(2, this.reconnectCount)); this.reconnectCount++; console.log(`[SocketManager] schedule reconnect in ${delay}ms, count=${this.reconnectCount}`); this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null; this.connect(url, options); }, delay); }步长从2秒开始,每次乘2,上限30秒。这样在服务端长时间不可用的情况下,客户端的重连频率会逐渐降低,而不是以固定频率疯狂请求。
消息发送的封装也要注意防呆:
send(data) { if (this.state !== SocketState.OPEN) { console.warn('[SocketManager] send fail, socket not open'); return false; } const payload = typeof data === 'string' ? data : JSON.stringify(data); this.socketTask.send({ data: payload, fail: (err) => { console.warn('[SocketManager] send fail', err); } }); return true; }3.3 页面订阅与生命周期协同
页面订阅的核心是让业务页面不关心连接是怎么建立的,只关心消息来了怎么处理。我会在管理器里维护一个监听函数集合:
on(event, handler) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event].push(handler); } off(event, handler) { if (!this.listeners[event]) return; const idx = this.listeners[event].indexOf(handler); if (idx !== -1) { this.listeners[event].splice(idx, 1); } } emit(event, payload) { if (!this.listeners[event]) return; this.listeners[event].forEach((handler) => { try { handler(payload); } catch (e) { console.error(`[SocketManager] listener error for event ${event}`, e); } }); }在页面里使用时,比如一个聊天页或者订单详情页:
Page({ onLoad() { this.socket = require('../../utils/socket-manager'); this.handleMessage = (data) => { // 这里拿到服务端推送的数据,更新页面 console.log('收到推送消息:', data); this.setData({ latestMessage: data }); }; this.socket.on('message', this.handleMessage); }, onUnload() { // 页面卸载时一定要解绑监听,否则会造成回调泄漏 this.socket.off('message', this.handleMessage); }, onShow() { // 每次页面展示时检查连接状态,如果需要就重新连接 if (!this.socket.isOpen()) { this.socket.connect('wss://example.com/socket', { header: { token: wx.getStorageSync('token') } }); } } });页面里的订阅、解绑、连接恢复三步,都是我在实际项目里验证过必要的。特别是onUnload里忘记off的坑,一旦页面被频繁打开关闭,消息回调会越积越多,最终表现为页面越来越卡、数据错乱。
3.4 数据格式与业务协议定义
WebSocket收到或发送的数据,格式一定要在项目初期约定好。我建议统一使用JSON作为传输格式,同时约定一个消息信封,把消息类型、消息体、发送时间统一打包。这样可以方便地做类型路由和日志追踪。
一个常见的协议结构:
{ "type": "order_status_changed", "data": { "orderId": "20241228001", "status": "PAID" }, "timestamp": 1700000000000, "msgId": "c3f8c6f2-9f4e-4b8a-9d1a-2f6e8d5a0e1c" }msgId是每条消息的唯一编号,尤其在处理客户端发消息的服务端ACK场景中,负有对账责任。客户端发送时可以带上一个自己生成的msgId,服务端处理完推送ACK时把这个字段原样带回来,客户端就知道“这条已经成功了”。服务端主动推的消息,也给一个msgId,客户端可以用来做去重。
数据解析时,建议在消息入口处做统一预处理,而不是让业务方各自解析。比如:
handleRawMessage(rawData) { let json = rawData; if (typeof rawData === 'string') { try { json = JSON.parse(rawData); } catch (e) { console.warn('[SocketManager] invalid json payload, raw:', rawData); return; } } if (!json || !json.type) { console.warn('[SocketManager] message missing type field'); return; } this.emit('message:' + json.type, json.data); this.emit('message', json); }这样一来,业务方既可以精确监听某个消息类型,也可以做全局的兜底处理,灵活性高了不少。
4. 常见问题与排查技巧实录
4.1 连接不稳定、频繁掉线怎么办
这个可以重点说。微信官方没有明确给出WebSocket连接保活的保证,实际使用中掉线是常态,不掉的只是运气好。
第一步是排查掉线的类型。我在实际项目里会先给onSocketClose加上详细的日志,把关闭码code和原因reason打出来。微信小程序里常见的关闭码有几个值得关注:1006是异常的连接关闭,通常对端不存在或网络层直接断掉;1000是正常关闭,可能是服务端主动踢的;1001是服务端重启或client主动离开;1002是协议错误。如果看到大量1006,基本可以确定是网络链路问题,大概率不是代码逻辑错误。
第二步是确认心跳是否在正常跑。很多掉线是因为服务端空闲超时,比如Nginx或云端负载均衡默认会清理空闲连接。你可以在服务端日志里看最后一条客户端消息时间戳,如果时间戳离掉线时刻很近,说明心跳没有发出去或者发出去没被处理。解决办法是检查心跳定时器有没有在页面切后台时被意外清掉。
第三步是检查域名与证书。wss连接对证书的要求比HTTPS更严格,部分云服务商的中间证书链不完整,浏览器里访问正常但小程序真机连接失败。这种情况在开发者工具里还很难复现,只有真机才报错。建议真机调试时,用wx.connectSocket的fail回调输出错误信息,再对照证书链检查。
4.2 连接成功但收不到消息的排查路径
这个话题在社区里也经常被问到,微信开发者工具里模拟测试时一切正常,真机上却时而能收消息时而收不到。
优先检查消息事件绑定时机。wx.onSocketMessage必须在connectSocket之后注册吗?我遇到过的场景是某些网络环境下,服务端在握手完成后立即推送了第一批数据,但客户端的onSocketMessage还没绑上,这一批消息就被丢掉了。所以绑定监听的动作要尽可能靠前,最好在socketTask.onOpen还没触发之前就完成。
接着检查服务端是不是只在连接建立时发送了数据,之后就没有再推送。有些服务端实现会判断客户端没有订阅就默默丢弃,如果使用的是第三方消息服务,要看它是否要求客户端先发一个“subscribe”指令,服务端才愿意推数据过来。我们在做消息推送时,确实遇到过连接正常但服务端等你先发订阅消息的坑。
最后检查数据格式兼容性。小程序端如果指定了ArrayBuffer作为消息类型,接收到的数据就不会是字符串,除非你做类型判断,很典型的“连接正常但数据进不了JSON.parse”的情况。建议始终用字符串类型接收,除非业务确实需要二进制流。
4.3 踩过的兼容性深坑记录
这里记录几个值得注意的兼容性细节。
第一个是安卓和iOS在WebSocket层面的差异。安卓部分机型在网络信号不稳定时,系统底层会主动断开TCP长连接,表现就是WebSocket毫无预兆地关闭。iOS上微信小程序的WebSocket表现相对稳定,但锁屏时间长了也会被系统挂起,解锁后才能恢复。这也就是说,无论哪个平台,都不能指望连接一直保持,重连机制必须是标配。
第二个是开发者工具、iOS真机、安卓真机三端对关闭码和错误信息的友好度完全不同。同一段代码,工具里给你打印一个清爽的错误对象,安卓返回一个罗马数字一样的关闭码,iOS干脆什么都不给。排查到后面我就学乖了,自己写日志模块,把所有关键节点的时间、状态、回调参数都打点记录下来,形成本地日志文件,遇到问题时直接导出,比在哪个平台上盲猜都高效。
第三个是关于App切后台。小程序切后台之后,定时器会被微信挂起。这意味着你的心跳定时器并不可靠,可能后台10分钟后才恢复执行一次,而这段时间内连接早就被服务端断掉了。所以我在App级监听里加了wx.onAppHide和wx.onAppShow的处理,切后台时记录时间戳,切回来时如果距离上次心跳超过60秒,直接触发一次重新连接。
4.4 抓包调试:用Charles排查小程序WebSocket问题
小程序里WebSocket的问题,有时候靠日志也难定位, 尤其是涉及握手协议、请求头、服务端返回异常的时候。这时候抓包工具就派上用场了。Charles是一个很常用的HTTP/HTTPS调试工具,也能支持WebSocket帧的查看。
抓包的思路是这样的:手机和电脑连着同一个局域网,在Charles里开启代理,手机Wi-Fi配置代理指向电脑的IP和Charles的监听端口。然后安装并信任Charles生成的HTTPS证书,因为小程序请求的是wss,属于加密通道,不装证书看到的全是乱码。证书安装好之后,就可以在Charles的会话列表里看到小程序的连接请求,点进去在WebSocket标签页里就能逐帧查看ping、pong以及业务消息的收发时序。
这招在排查“连接建立后消息不及时”“发送的消息格式不对但服务端不报错”这类问题时尤其好用。你可以清清楚楚看到客户端发出的JSON原始内容和服务端返回的原始帧,不用再依赖两端日志来相互猜。有一点要注意,必须在开发者工具和手机上都关闭SSL校验或者完成证书信任,否则抓包环境下连接会失败,这个不属于异常,解开代理后恢复正常。
微信小程序目前对局域网抓包本身没有限制,但如果你的项目用了较强的服务端校验(比如只允许特定header的请求),抓包时看到的报文和正式环境会有差异,遇到这类问题时可以先在服务端把校验临时放宽,定位到协议层问题后再恢复。
4.5 常见问题速查表
| 现象 | 可能原因 | 排查优先级 |
|---|---|---|
| 连接一直停留在connecting | 域名未加入合法域名白名单、证书链不完整、网络被墙 | 先看fail回调,再加超时保护 |
| 连接秒断或频繁掉线 | 空闲超时、心跳未生效、NAT清理 | 服务端日志 + 心跳时间戳 |
| 消息发不出去 | 连接尚未open就调用send | 确保状态为OPEN再发送 |
| 消息偶尔收不到 | 事件绑定太晚、服务端等待订阅 | 检查绑定时机,提前到onOpen前 |
| 工具正常真机异常 | 证书链问题、平台差异 | 真机日志导出,抓包对比 |
| 切后台回来消息断供 | 定时器被挂起、连接已被服务端断开 | 监听AppHide/AppShow,超时重连 |
| 消息重复收到 | 重连后旧连接未清干净 | 确保旧连接close后再发起新连接 |
5. 从基础封装到业务场景:几个有必要提前思考的扩展点
WebSocket做到能连、能收、能发,只算是入门。真正让连接稳定、让业务放心的,往往是前面几节的工程细节之外的一些额外设计。这里再分享几个我在实际项目里觉得很有用的扩展方向。
比如小程序商城场景下的消息推送。用户在订单详情页等待支付结果,这时候服务端在用户完成支付后通过WebSocket推送一条订单状态变更消息,前端收到后立即刷新页面,而不需要用户手动刷新。要注意的是,用户在支付页面跳转的间隙,小程序可能已经切到了后台,这时候消息来了前端可能错过,所以除了WebSocket推送,还要在onShow里补一次HTTP拉取,做兜底。
再比如uniapp这类跨端框架。uniapp本身封装了uni.connectSocket,API风格接近WebSocket标准,能在多端复用。用uniapp做小程序时,连接管理的方式和原生版本大致相同,但要额外注意编译到不同平台(微信小程序/H5/App)时WebSocket API的差异。我的经验是,如果只面向微信小程序,直接用原生API最省心;如果一定要跨端,把连接层提取成平台适配文件,不要写死在业务里。
还有React Native或H5里常见的SSE方案,在小程序里并不原生支持,所以如果你在小程序里需要类似SSE的实时推送效果,WebSocket基本是唯一选择。如果WebSocket只是偶尔掉线重连,则务必不要在使用它的同时引入另一套轮询逻辑,避免双通道消息重复、流量加倍和页面刷新时的数据闪烁。
最后补充一个非常实用但常被忽略的设计思路:服务端主动踢用户下线或重新登录的通知,也应该走WebSocket通道下发。比如账号在另一台设备登录,可以通过WebSocket推送一个force_logout消息,让当前设备立即退出登录。这种体验比靠HTTP下拉刷新来判断要即时得多,也贴合小程序的交互习惯。
我从一开始做小程序WebSocket到现在,最大的体会是:WebSocket的“连接”不是一次性的状态,而是一条需要时刻照顾的生命线,从建连、鉴权、订阅、心跳到断线重连、消息去重、页面生命周期协同,每一个环节都必须刻意设计。把这一整套流程跑顺了,实时推送就是一个很自然的基座;跑不顺,每一条消息都像是在走钢丝。这篇内容里的代码和排查经验都是我从实际项目中整理出来的,你照着搭一遍、在自己项目的真机上验证一遍,大概率能避开大部分前人踩过的坑。