这是《数字展厅 Unity 项目开发实战分享》系列的第三篇,聚焦展厅导视系统里的 MQTT 消息设计:从主题划分与注册上线握手,到实际运行中暴露的解析、去重、断线与资源落地问题。文中涉及的后台地址、服务凭据、设备 IP 一概不展开。

本页目录

  1. 3.1 为什么选 MQTT
  2. 3.2 主题与握手
  3. 3.3 遇到的问题与解决办法

点击目录可直达对应小节;页面各小节标题均已设置锚点 id,便于外链引用与搜索引擎抓取。

3.1 为什么选 MQTT

一体机要求"断网能用、联网就能同步":功能全在本地跑,后台只负责内容更新推送与在线状态接收。这种"少量、可靠、双向、面向设备"的通信,MQTT 比轮询 HTTP 合适得多——设备侧一个长连接,服务端按设备定向下发,正契合"一个后台管多台设备"的模型。

3.2 主题与握手

主题设计刻意极简,一共三类:

主题方向载荷说明
设备 ID(MAC 小写)下行UpdateSignal / UpdatePoints设备专属主题,后台按设备定向推送
status上行{id, status:"online", name}上线通告
status下行status == "gistered"注册通过回执
laststatus上行{id, off:"1"}离线通告

握手流程:未注册设备启动后订阅自己的设备主题,等服务器下发注册通过回执,再把设备名与注册态写进 PlayerPrefs;已注册设备直接向 status 主题发布上线消息,再订阅自身主题。程序退出时向 laststatus 发一条离线消息并断开连接,让后台的在线状态不至于"假在线"。

下行消息只有两种结构,字段少、语义单一,这本身就降低了联调成本:

消息类型字段作用
UpdateSignaltable = "stb"待机资源更新,携带图片/视频地址列表
btm底部滚动字幕文本
UpdatePointstable需要更新的点位标识
distance / angle该点位的距离与机械臂指向角度
res = "1"表示附带资源,需触发下载

3.3 遇到的问题与解决办法

问题:一条脏数据拖垮整批消息。

早期解析逻辑是一个大 try,只要一条 JSON 解析失败,后面的消息全部丢失。改成按消息类型分别 try-catchUpdateSignalUpdatePoints 各自独立),单条异常只丢弃自己,其余照常处理。

问题:后台连续推送导致下载堆积。

后台更新一个模块可能连发多条消息,直连下载会重复下载、并发打满带宽。三层防护:HashSet 去重(同地址只入队一次)、0.8s 去抖(一批消息合并成一次任务)、_isDownloading 锁(防两次下载重叠)。

问题:待机资源和点位资源互相覆盖。

两类资源共用一个去抖协程时,后到的消息会把前一类任务的计时重置,出现"待机素材更新了、点位图没更新"。改成两条独立的去抖通道,各自维护计时与任务。

问题:下载完成回调不在主线程。

网络回调里直接操作 UI 是典型的偶发崩溃源。统一把落地操作(改界面、写文件、触发下一步)经主线程调度器转回主线程执行。

问题:断线与重连策略。

连接参数加 60s KeepAlive 维持长连接,断线后延时 5 秒重连,避免服务端异常时疯狂重试;会话采用不保留模式(CleanSession),设备重启后通过注册/上线流程重新对齐状态——对"状态以服务端为准"的业务,不保留会话反而更干净。

问题:资源落地的原子性。

下载目录与实际使用目录分开:先下到 Temp,确认完整后再移动到 New,移动时先清目标再搬运。中途掉电最坏只是 Temp 里多个临时文件,不污染正在展示的资源;程序退出时在 OnDestroy 里再补一次移动。

问题:本地数据写回时机。

点位信息更新后写回 PointsInfo.json 目前依赖离场/退出时触发,运行中途掉电可能丢失这一批更新。这是当前工程已知的隐患,改进方向是"下载完成即写回",把数据落盘与进程生命周期解耦。

顺带一提流量与画质上的优化:图片下载后统一做质量压缩与方向修正,单张体积从 13MB 级别降到 3~5MB 级别,同时按 EXIF 修正方向,避免现场出现"图是倒的";下载设 30s 超时并失败重试 1 次,兼顾弱网与不卡死。

回头看这一方向,能沉淀成方法的其实就一条:消息设计要假设它会脏、会重、会断,去重、去抖、分通道、可重入。都不是什么高深技术,但都是被现场问题反复教育出来的。

(内容由AI生成,仅供参考)

想聊聊您的项目?

从技术选型到落地实施,欢迎随时交流。

联系我们