ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微信小程序长连接实战:WebSocket连接、心跳重连与二进制协议设计

微信小程序长连接实战:WebSocket连接、心跳重连与二进制协议设计 不少做物联网、智能硬件、工业数据看板的朋友迟早都会碰到一个需求小程序里要跟设备保持实时双向通信。HTTP轮询太笨延迟高、流量浪费大消息稍微密集一点就卡。于是大家盯上了TCP长连接。但微信小程序并不是浏览器它虽然有wx.connectSocket这类接口可它跟后端真正建立起来的、能被开发者直接操控的通道其实是 WebSocket 协议。换句话说小程序里的“TCP通信”99% 的落地形态都是 WebSocket。这篇内容我就从实际项目出发把小程序里做 TCP/WebSocket 长连接通信的整套流程、坑点和协议设计思路都梳理一遍。不管你是做设备控制、订单推送、IM 聊天还是要对接自研的 TCP 网关这篇文章基本能帮你从零跑通并且避开我当年踩过的那些坑。1. 为什么要在小程序里做“TCP通信”1.1 别被名字带偏小程序里的TCP其实是指WebSocket先把这个概念掰扯清楚微信小程序没有直接暴露new Socket()这种原生 TCP 接口你不能在小程序里像在 Node.js 里那样net.connect(port, host)。小程序提供给开发者的长连接 API 是wx.connectSocket底层走的是 WebSocket 协议。WebSocket 本身是建立在 TCP 之上的所以很多人叫它“小程序TCP通信”本质上是“TCP长连接 应用层 WebSocket 握手”。为什么微信不直接开放裸 TCP原因很简单安全管控。小程序跑在微信这个宿主环境里如果每个小程序都能随意向任意 IP 端口发起 TCP 连接那恶意连接、内网探测、数据泄露这些问题全都堵不住。所以微信做了一层协议约束必须走 wss 或 ws正式环境下必须 wss必须把域名配置到白名单里。理解了这个大前提你后面遇到“连不上、真机不通”这类问题时排查方向就不会跑偏。那么普通开发者怎么感知到 TCP 的存在主要靠两个地方一是连接 URLwss://yourdomain.com:port/path这里域名背后解析到的服务器 IP 和端口其实就是 TCP 层的目标二是消息的可靠性TCP 的确认重传、有序传输等能力在 WebSocket 层基本都被保留了下来。换句话说你不需要关心 TCP 的滑动窗口和拥塞控制但可以享受它的可靠性。1.2 哪些业务必须走Socket长连接而不是HTTP轮询我经常被问到“我这个需求明明用 HTTP 也能做为什么要上长连接”这里我给出一个比较实用的判断标准。如果你的业务满足下面任意两条长连接基本就是刚需服务端需要主动推送消息比如有人下单了、设备报警了、新版本发布了。端上需要实时状态同步比如大屏数据、在线设备列表、游戏对局状态。交互频率高HTTP 轮询会造成大量无效请求比如聊天、协作编辑。单条消息延迟要求高比如遥控设备、抢单轮询的延迟不可控。举个例子我之前做的一个设备远程控制项目用户在小程序里点一下“开门”指令要立刻到达设备端。如果走 HTTP 轮询设备端每隔 3 秒拉一次指令用户按下按钮后最坏要等 3 秒门才开体验很糟糕。改用长连接之后指令从发出到设备收到基本在 500 毫秒以内而且设备的状态变化也能实时回传用户体验完全不一样。另外还有一类典型场景服务器端做数据汇聚。比如你有一批传感器通过网关把数据发到服务端用户在微信小程序里查看实时曲线。如果每次都靠小程序主动拉服务端就得为每个小程序用户维护一个轮询状态纯属浪费资源。用长连接服务端主动把数据“推”给小程序小程序只负责渲染架构清晰很多。1.3 微信给的限制清单提前知道心里有底在小程序里玩长连接有几个硬性限制是绕不过去的提前知道能省很多排查时间并发 Socket 数量同一个小程序同时最多只能有 5 个 WebSocket 连接。注意是“同时”超过的会被微信直接拒绝或者最老的那个被踢掉。域名白名单wx.connectSocket的 URL 必须匹配“socket合法域名”否则开发者工具里能过真机上一律连不上。正式环境强制要求 wss也就是需要在服务器上配置 SSL 证书。前后台状态小程序切到后台Socket 连接并不会立刻断开但长时间在后台系统随时可能把它回收。回到前台时不能假设连接一定还在必须主动检查并重连。数据大小单次通过wx.sendSocketMessage发送的数据长度有限制实际上比较稳妥的做法是控制单包体积二进制数据尤其要注意分包。开发者工具与真机差异开发者工具里模拟的是桌面浏览器的网络环境真机走的是手机网络运营商可能对长连接有 NAT 超时、空闲断开等策略所以“工具里好好的真机一测就断”这种情况太常见了。这些限制在项目启动之前就得排进架构里。比如连接数只有 5 个如果业务里既要 IM又要设备控制还要订阅服务那就要考虑是不是复用同一条连接用业务层协议来区分消息类型而不是傻傻地开多个 Socket。2. 动手前的准备工作域名、工具与项目结构2.1 socket合法域名配置这一步最容易卡住新人很多新手第一次碰小程序长连接写完代码在开发者工具里跑得好好的一上真机就报url not in domain list。这个错的具体意思就是你wx.connectSocket里的地址不在微信后台配置的合法域名里。配置入口在微信公众平台的后台路径是开发 → 开发管理 → 开发设置 → 服务器域名 → socket合法域名。在这里添加你的域名注意几个细节域名必须已经完成 ICP 备案否则填不进去。域名不能是 IP必须是一个真正的域名比如wss://socket.example.com不能写wss://120.25.x.x。端口可以带比如wss://socket.example.com:8088微信会把它整体作为一个合法域名来校验。配置生效不是即时的通常需要几分钟到十几分钟别刚改完就去真机试容易误判。这里有个容易混淆的点很多人以为在开发者工具里勾选了“不校验合法域名”就万事大吉但这只是本地调试用的真机完全不认这个开关。所以项目上线前一定要确认域名已经加到白名单而且服务器上的证书是可信的、没有过期。2.2 开发者工具里的调试选项与“本地调试”技巧开发阶段做联调最烦的就是域名还没备案、服务器还没上公网。这时候有两个临时方案在开发者工具的“详情 → 本地设置”里勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”。这个选项打开后本地调试时你可以连接任意 ws:// 地址比如ws://192.168.1.10:9090方便跟内网服务器联调。但这个只对开发者工具有效。如果你要用真机预览也得在预览时打开“开发版”调试模式在真机上同样可以绕过域名校验。具体操作是右上角菜单 → 打开调试会在小程序界面上出现一个 vConsole 的悬浮按钮这时候网络请求和 Socket 都不会强制走域名白名单。注意真机调试下的 Socket 连接不会被 Charles 这类 PC 端抓包工具直接看到因为小程序真机流量是加密且由微信客户端管理的这点我在后面的排查部分会专门提到。2.3 开发框架选型原生、uni-app、Taro 的差异小程序开发框架现在主要有三条路线微信原生、uni-app、Taro。如果你只是做一个微信小程序我建议直接用原生API 最直接也不存在编译层带来的调试差异。但如果你有跨端需求比如同一套代码要跑支付宝小程序、H5、App那 uni-app 或 Taro 会是更现实的选择。以 uni-app 为例它其实是对wx.connectSocket做了一层封装对应的接口是uni.connectSocket回调风格也做成了类似浏览器 WebSocket 的写法。要注意的是uni-app 在 H5 端和 App 端会走浏览器或原生 WebSocket微信小程序端会转成小程序的wx.connectSocket所以有些细节在两端的表现会不一样比如ArrayBuffer的处理方式、消息事件的字段名差异等。不管用哪种框架核心的 TCP/WebSocket 逻辑我建议单独封装成一个模块不要和页面组件耦合。后面后端改协议、增加心跳、调整重连策略都只需要改一个文件页面层完全无感。这个习惯在项目变大之后会非常省事。3. 核心实现从连接建立到双向通信3.1 建立连接wx.connectSocket 的完整参数说明下面这段代码是原生小程序里建立 Socket 连接的基本写法我加上了详细注释。function connectSocket() { const socketTask wx.connectSocket({ url: wss://socket.example.com:8088/ws, header: { X-Token: getToken() // 自定义鉴权头服务端可以校验 }, protocols: [my-protocol], // 可选子协议服务端需要对应支持 tcpNoDelay: true, // 这个参数在部分平台生效控制是否禁用 Nagle 算法 success() { console.log(连接发起成功注意这不是连接建立成功); }, fail(err) { console.error(连接发起失败一般是域名/配置问题, err); } }); socketTask.onOpen(() { console.log(WebSocket 连接真正建立完成); // 这里可以发送登录鉴权消息、启动心跳等 }); socketTask.onMessage((res) { // res.data 可能是字符串也可能是 ArrayBuffer handleMessage(res.data); }); socketTask.onError((err) { console.error(Socket 发生错误, err); // 这里不要直接重连容易造成重连风暴 }); socketTask.onClose((res) { console.log(连接关闭code:, res.code, reason:, res.reason); // 在这里触发重连逻辑 }); return socketTask; }wx.connectSocket返回的是一个SocketTask对象后续的收发、关闭操作都通过它来调用。特别强调一下success回调只代表“发起连接”这个动作成功了并不代表连接已经建立。真正建立成功是onOpen触发的时候。新手在这里容易写错在success里就直接发消息结果发现服务端收不到。参数里的header可以用来带鉴权信息但注意这个 Header 不是 HTTP Header而是 WebSocket 握手阶段携带的额外字段。服务端在 WebSocket 握手时比如 Node.js 的ws库可以通过request.headers拿到。如果你希望更安全可以把令牌放在 URL 的 query 里比如wss://xxx/ws?tokenabc但这样 token 会被记到服务器日志里建议有效期设短一点。3.2 消息收发与 ArrayBuffer 字节处理微信小程序的 Socket 消息有两种数据类型string和ArrayBuffer。默认情况下onMessage返回的res.data是字符串。如果你的服务端发过来的是二进制数据比如自定义协议包、图片、音频流那么在小程序里需要在onMessage里手动转二进制或者在wx.connectSocket时通过header或其他方式告知服务端只发文本。一个更规范的做法是在消息事件里判断数据类型。微信小程序的onMessage拿到的res.data如果是二进制它实际上就是一个ArrayBuffer。你可以这样判断socketTask.onMessage((res) { const data res.data; if (typeof data string) { // 文本消息一般是 JSON handleTextMessage(JSON.parse(data)); } else { // ArrayBuffer需要按协议解析 const bytes new Uint8Array(data); handleBinaryMessage(bytes); } });发送二进制的时候你可以直接传ArrayBuffer给sendSocketMessageconst bytes new Uint8Array([0x01, 0x02, 0x03, 0x04]); socketTask.send({ data: bytes.buffer, success() { console.log(二进制消息发送成功); }, fail(err) { console.error(发送失败, err); } });这里有个容易忽略的点Uint8Array的buffer才是ArrayBuffer很多新手直接把Uint8Array传进去导致发送数据异常或者只发了第一个字节。还有如果你要拼接多个ArrayBuffer建议先用Uint8Array操作最后统一取.buffer不要直接在ArrayBuffer层面做拼接API 不方便也容易出错。实际项目中文本和二进制可以混合使用。比如控制指令用一段紧凑的二进制协议状态上报用 JSON 文本。这样做的理由是JSON 可读性强、便于调试但体积大、解析慢二进制协议紧凑、性能高但可读性差、需要严格定义字段。协议设计的选择我会在第四部分详细讲。3.3 心跳保活与断线重连含代码做过长连接的人都知道一句话长连接不是连上就完事了重点在保活和断线重连。尤其是移动端网络环境极不稳定Wi-Fi 切 4G、进电梯、地铁过隧道都可能导致连接被静默断开。更麻烦的是这种断开有时候 TCP 层根本感知不到因为运营商路由器可能在空闲超时后把连接杀掉但两端都没有收到 FIN 包连接看起来还“活着”实际上已经死了。解决这个问题只有一招主动探测——心跳包。小程序作为客户端每隔一段时间给服务端发一条心跳消息服务端收到后回一条确认。如果在规定时间内没收到任何数据就可以认为连接已经异常。心跳不只为了保活还能顺便检测链路质量。下面是我在项目里用的一套比较成熟的心跳和重连策略。class SocketClient { constructor(options) { this.url options.url; this.heartbeatInterval options.heartbeatInterval || 15000; // 心跳间隔15秒 this.reconnectMaxTimes options.reconnectMaxTimes || 10; // 最大重连次数 this.reconnectDelay options.reconnectDelay || 3000; // 重连基础延迟3秒 this.task null; this.heartbeatTimer null; this.reconnectTimer null; this.reconnectTimes 0; this.isManualClose false; } connect() { this.isManualClose false; this.task wx.connectSocket({ url: this.url }); this.task.onOpen(() { console.log(连接建立成功); this.reconnectTimes 0; // 重连成功后重置重连次数 this.startHeartbeat(); // 这里可以发送业务登录消息比如鉴权 token }); this.task.onMessage((res) { // 收到任何消息说明链路是通的可以重置心跳的计时基准 this.resetHeartbeat(); // 业务处理 }); this.task.onClose(() { console.log(连接关闭); this.stopHeartbeat(); if (!this.isManualClose) { this.scheduleReconnect(); } }); this.task.onError((err) { console.error(连接错误, err); // onError 有时候不会自动触发 onClose最好手动关闭连接再走重连流程 this.closeSocket(); }); } send(data) { return new Promise((resolve, reject) { if (!this.task) { reject(new Error(Socket 未连接)); return; } this.task.send({ data, success: resolve, fail: reject }); }); } startHeartbeat() { this.stopHeartbeat(); this.heartbeatTimer setInterval(() { this.sendHeartbeat(); }, this.heartbeatInterval); } sendHeartbeat() { const heartbeatMsg JSON.stringify({ type: ping, ts: Date.now() }); this.task.send({ data: heartbeatMsg, fail: (err) { console.warn(心跳发送失败, err); // 连续发送失败几次可以考虑主动断开连接 } }); } resetHeartbeat() { // 简单实现收到任意消息都清掉计时器重新开始 this.stopHeartbeat(); this.heartbeatTimer setTimeout(() { this.sendHeartbeat(); }, this.heartbeatInterval); } stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer null; } } scheduleReconnect() { if (this.reconnectTimes this.reconnectMaxTimes) { console.error(重连次数达到上限停止重连); return; } const delay this.reconnectDelay * Math.pow(2, this.reconnectTimes); // 指数退避 this.reconnectTimes 1; console.log(将在 ${delay}ms 后进行第 ${this.reconnectTimes} 次重连); this.reconnectTimer setTimeout(() { this.connect(); }, delay); } close() { this.isManualClose true; this.stopHeartbeat(); this.closeSocket(); } closeSocket() { if (this.task) { try { this.task.close({}); } catch (e) { // 忽略 } this.task null; } } } module.exports SocketClient;心跳间隔怎么选太短浪费流量和电量太长会导致故障发现不及时。我的经验值是业务消息频率高的时候15 秒一次心跳足够如果业务本身就是秒级高频交互甚至可以不单独发心跳靠业务消息就能判断链路状态。但如果你做的是低频设备控制比如半小时才操作一次那么心跳间隔最好设置在 10 到 30 秒之间。断线重连为什么要用指数退避因为你如果每隔固定 3 秒重连一次一旦服务端出故障大量客户端会同时发起连接形成“重连风暴”把服务端彻底打垮。指数退避的意思是第一次失败等 3 秒第二次等 6 秒第三次等 12 秒逐步加大间隔让重连请求错峰。最多重连 10 次后如果还没成功说明网络或服务端有大问题这时候就不该无脑重连了应该提示用户手动刷新。4. 传输协议设计从裸 Socket 到可靠消息4.1 粘包和半包问题做 TCP 编程的人都知道粘包和半包WebSocket 协议本身就解决了这个问题因为它基于帧传输一条消息就是一个完整的 frame。所以你在小程序里用 WebSocket 收发消息不会遇到传统 TCP 的粘包问题。但有个前提你必须在“一条 WebSocket 消息”里放完一个完整的业务包。如果你习惯性地把多条业务消息拼在一个大字符串里一次性 send或者把一条业务消息拆成多个 send那么服务端或小程序端接收时就会出现类似粘包、半包的问题。很多从裸 TCP 转过来的后端同学容易在这一点上栽跟头在小程序里一次性 send 了一长串 JSON结果服务端收到的不是一次完整数据而是被拆成了多帧或者在服务端把多个业务消息塞进一个 WebSocket 帧里发过来小程序端onMessage一次性拿到一大坨需要自己再拆分。所以我给的建议是一条 WebSocket 消息只承载一个完整的业务消息发送前先做好数据的序列化接收时直接按消息粒度解析。4.2 自定义二进制协议设计举例文本协议JSON开发调试方便但体积大、解析慢。如果你的业务场景是高频控制指令或者对带宽和速度敏感我建议定义一套紧凑的二进制协议。下面是一个我实际用过的协议格式示例消息头5字节 - magic (1字节): 固定值 0x5A用来校验帧头 - version (1字节): 协议版本号 - type (1字节): 消息类型比如 0x01 表示心跳0x02 表示控制指令 - length (2字节): 消息体长度大端模式 消息体length字节 - 具体业务数据比如设备ID、控制码、参数对应的生成和解析代码可以这样写function encodePacket(type, payloadBuffer) { const header new ArrayBuffer(5); const headerView new DataView(header); headerView.setUint8(0, 0x5A); headerView.setUint8(1, 0x01); headerView.setUint8(2, type); headerView.setUint16(3, payloadBuffer.byteLength, false); // false 表示大端模式 const packet new Uint8Array(5 payloadBuffer.byteLength); packet.set(new Uint8Array(header), 0); packet.set(new Uint8Array(payloadBuffer), 5); return packet.buffer; } function parsePacket(buffer) { const bytes new Uint8Array(buffer); if (bytes.length 5) { return null; } const view new DataView(buffer); const magic view.getUint8(0); if (magic ! 0x5A) { throw new Error(错误的帧头); } const version view.getUint8(1); const type view.getUint8(2); const length view.getUint16(3, false); if (bytes.length ! 5 length) { throw new Error(消息不完整可能还有后续分片); } const payload bytes.slice(5, 5 length); return { version, type, payload }; }设计协议时有几个小建议大端还是小端要固定下来推荐大端也就是网络字节序通用性最好。尽量用无符号整数存长度字段避免符号位带来的负数问题。协议里一定带版本号方便以后升级不至于因为一个字段调整导致新老版本直接在公司群里互相扯皮。magic 字段别省它能挡住大部分“连错服务器、收到脏数据”的情况。4.3 超时与错误处理长连接除了断线还可能遇到“消息发出去了但一直没收到响应”的情况。这在控制类业务里特别致命用户点了“开门”界面一直转圈谁都不知道到底开没开。所以应用层一定要有超时机制。最简单的做法是发送请求时生成一个唯一的msgId把msgId和发送时间记在一个 Map 里同时启动一个定时器。收到响应时根据响应里的msgId找到对应条目清除定时器。如果定时器到点还没收到响应就认为这次请求超时提示用户重试。const pendingMap new Map(); let nextMsgId 1; function sendWithAck(type, payload, timeout 5000) { const msgId nextMsgId; const packet encodePacket(type, packWithMsgId(msgId, payload)); return new Promise((resolve, reject) { const timer setTimeout(() { pendingMap.delete(msgId); reject(new Error(请求超时)); }, timeout); pendingMap.set(msgId, { resolve, reject, timer }); socketTask.send({ data: packet }); }); } function onMessageReceived(buffer) { const { type, payload } parsePacket(buffer); const msgId extractMsgId(payload); if (pendingMap.has(msgId)) { const pending pendingMap.get(msgId); clearTimeout(pending.timer); pendingMap.delete(msgId); pending.resolve(payload); } }这套机制写起来不难但能解决大量实际问题。尤其是“看起来连接还活着但服务端已经没在处理”的情况超时能帮你及时发现问题而不是让用户一直干等。5. 真机调试与抓包排查实录5.1 常见的连不上、收不到数据案例第一类问题真机上报“url not in domain list”。前面提过这是域名白名单问题。解决办法是后台配置 socket 合法域名然后等几分钟再试。要注意的是如果你连接的是ws://不带 s真机上是连不上的正式环境必须wss://。第二类问题开发者工具一切正常真机连不上。这种情况大概率是服务器防火墙没有放行 wss 对应的端口或者 SSL 证书链不完整。用手机浏览器直接访问https://你的域名:端口如果能正常打开且浏览器地址栏显示锁形图标说明证书没问题。如果浏览器都报证书错误那就要先修证书。第三类问题连接能建立但收不到消息。我排查过几次原因出在服务端发送的是二进制数据小程序端按字符串解析导致乱码或直接忽略。这时候在onMessage里先console.log打印一下res.data的类型确定是字符串还是ArrayBuffer再决定解析逻辑。还有一种情况是服务端发消息的时候用了send的fin字段或混淆了 WebSocket 的 close 和 send这类问题要用抓包工具对比才能发现。第四类问题连接建好之后过几分钟就被断开。大多数是 NAT 超时或服务端主动踢人。解决方法就是用心跳让连接一直有数据流动运营商的 NAT 表项就不会因为空闲被删掉。如果心跳都发了还是被断检查一下服务端有没有按来源 IP 做连接数限流或者对空闲连接设置了最大生存时间。现象可能原因排查优先级真机url not in domain listsocket 合法域名未配置或未生效高开发者工具正常真机连不上端口未放行 / 证书错误 / 域名未备案高连接成功但收不到数据数据类型不一致 / 服务端发送异常中连接过几分钟被断开NAT 超时 / 服务端空闲踢人 / 心跳缺失中发送消息失败Socket 已断开未感知 / 发送数据格式错误中重连失败次数过多服务端恢复慢 / 重连策略太激进低5.2 charles 等工具抓包要点与局限调试小程序网络请求很多人习惯开 Charles但要注意PC 端 Charles 默认只能抓到 HTTP/HTTPS 的请求对 WebSocket 连接只能看到升级握手那一下后面的数据帧在 Charles 里看不到明文的 WS 帧。而且真机小程序走的是微信客户端自身管理的网络栈你在 PC 上通过代理抓包经常连握手都看不到。如果你用开发者工具做调试可以打开 console 面板给socketTask挂上onMessage和onSend日志这是最直接的方式。或者接一个 vConsole在真机页面上直接看 Log。真机上如果开启“调试”模式vConsole 会浮在小程序页面上console.log的信息都能看到。要抓 WebSocket 帧内容我比较推荐用服务端日志来配合在小程序端打日志同时在服务端把每条收到的原始帧打印出来两边时间戳一对比就能快速定位是“没发出去”、“发出去了服务端没收到”还是“服务端回了但小程序没解析”。5.3 上线前必做的连接检查清单这里列一个我在每次发版前都会过一遍的检查清单照着做基本能防住大部分线上事故socket 合法域名已配置且证书在有效期内用手机浏览器验证过直连没问题。小程序前后台切换时能正确处理连接状态后台挂起时间长了回前台能自动重连。心跳间隔与业务消息频率不冲突心跳发送失败连续 3 次会主动断开并触发重连。重连有最大次数限制避免无限重连造成服务器压力。所有发送给服务端的数据都做了长度校验单帧数据控制在合理范围内。消息里带msgId超时未响应有明确提示不会让用户等死。服务端有连接数监控能对异常断连进行告警。小程序版本更新时旧版本的长连接不会被新版本顶掉或造成冲突服务端需要做连接的账号维度管理。另外一个容易被忽视的点小程序的wx.connectSocket在 Android 和 iOS 上的底层行为有一些差异。iOS 上 WebSocket 掉线后重连的触发时机通常更及时Android 部分定制 ROM 对网络权限控制更严格后台息屏后 Socket 大概率会被杀掉。所以 Android 机的用户更容易感知到“过一会儿就掉线、再点进来要等重连”这是正常的不要以为是自己代码出了问题。6. 从项目实战再往后的一点心得我个人的习惯是只要小程序里用了 WebSocket服务端就一定配一套连接状态监控。连接数、消息量、心跳成功率、重连次数这几个指标比什么都重要。有一次我们线上出现大面积掉线就是靠监控发现半小时内重连请求突然翻倍排查后确定是服务端一台机器负载过高导致 accept 变慢。如果没有监控这种问题可能得等用户投诉才会暴露。另外协议设计上尽量不要把逻辑写在 frame 层。也就是说WebSocket 帧里承载的是业务消息业务消息里再用 JSON 或者二进制自定义协议去区分具体动作。这样将来就算把 WebSocket 换成 MQTT 或者其他传输层业务逻辑也还能复用。最后分享一个做设备控制类项目的小技巧把“连接状态”做成全局可观察的数据。页面进入时订阅连接状态一旦断线界面上立刻显示一个“连接已断开正在重连”的提示条重连成功再自动消失。这比用户点了按钮没反应才意识到掉线要友好得多。这个状态同步可以通过全局 store比如 mobx、vuex来实现原生小程序也可以用getApp().globalData加事件订阅来做。状态可视化是长连接项目里最能提升体验的一个小细节强烈建议加上。
返回列表