移动端用 websocket 连 mqtt 服务本质是将标准 mqtt 报文封装于 websocket 隧道(ws/wss)中传输,而非协议合并;需 broker 显式启用 websocket 监听器(如 emqx 的 8083 端口)、客户端匹配 protocolid/protocolversion、nginx 透传 upgrade 头并禁用缓冲,且服务端必须校验 topic 权限与鉴权。
移动端用 websocket 连 mqtt 服务,本质不是“协议合并”,而是让 mqtt 协议跑在 websocket 传输层上——ws:// 或 wss:// 只是隧道,里面传的仍是标准 mqtt 报文。网关要做的不是“转换 mqtt 到 websocket 消息格式”,而是正确暴露 mqtt over websocket 的接入端点,并处理好连接生命周期、认证、主题映射和上下行透传逻辑。
MQTT Broker 必须启用 WebSocket 监听器
很多初学者卡在第一步:直接用 mqtt.connect('ws://localhost:1883'),但 1883 是 TCP 端口,不支持 WebSocket。Broker 必须显式开启 WebSocket 支持,且监听在独立端口(如 8083/8084)。
- EMQX:检查
etc/emqx.conf中是否启用listener.ws.external = 8083,并确认listener.wss.external(SSL)配置了证书路径 - Mosquitto:需在
mosquitto.conf中添加listener 8083+protocol websockets,不能复用 TCP listener - 阿里云 IoT 平台、腾讯云 IoT Explorer 等公有云服务,WebSocket 接入地址通常形如
ws://xxxx.iot.tencentcloudapi.com/mqtt,必须带/mqtt路径后缀,否则握手失败
客户端连接参数必须匹配 Broker 的 MQTT 协议版本与协议标识
浏览器或小程序里用 mqtt.js 连接时,protocolId 和 protocolVersion 错一个,就会收到 Connection refused: Not authorized 或静默断连——这不是鉴权问题,是协议握手不通过。
- EMQX 5.x 默认要求
protocolId: 'MQTT'+protocolVersion: 4(即 MQTT 3.1.1);若设成5会失败 - 某些旧版 Mosquitto 只支持
protocolVersion: 3,此时protocolId应为'MQIsdp'(注意大小写) -
clientId必须唯一且非空;微信小程序因 sandbox 限制,不能用Math.random(),建议用设备 ID 或用户 token 衍生 - 务必设置
keepalive: 60,否则部分安卓 WebView 会在 30 秒无心跳后主动断连
Nginx 做反向代理时必须透传 Upgrade 头并禁用缓冲
如果在 Broker 前加了 Nginx(比如统一 TLS 终结或域名路由),默认配置会破坏 WebSocket 握手——HTTP Upgrade 请求被当成普通 HTTP 转发,导致 ERR_CONNECTION_REFUSED 或返回 200 HTML 页面。
- 必须在
location块中显式设置:proxy_http_version 1.1、proxy_set_header Upgrade $http_upgrade、proxy_set_header Connection "upgrade" - 禁用缓冲:
proxy_buffering off,否则 MQTT PUBACK 等控制包可能被缓存延迟,引发 QoS 1 消息重复或超时 - WebSocket 连接是长连接,
proxy_read_timeout至少设为 600(10 分钟),避免 Nginx 主动断开空闲连接 - 不要在 Nginx 层做 JWT 鉴权——MQTT CONNECT 包里的 username/password 或 custom auth 插件应在 Broker 层处理,Nginx 无法解析 MQTT 二进制帧
网关层的主题路由与权限隔离不能依赖前端传入的 topic
移动端客户端可任意指定 subscribe 或 publish 的 topic,比如 /user/123/msg 或 /sys/control。若网关不做服务端校验,攻击者可伪造 topic 订阅他人消息或发布非法指令。
- Broker 的 ACL(如 EMQX 的
etc/acl.conf)必须按username或clientid绑定可读写 topic 前缀,例如user/123/#,禁止通配符越界 - 网关若做二次路由(如把
/app/mobile/123映射到内部/core/device/abc),映射规则必须服务端维护,不可由客户端传参决定 - 微信小程序等受限环境,
connect时的username应由后端签发短期 token(如 JWT),Broker 插件解码后提取 user_id,再查库加载对应 ACL 规则
真正容易被忽略的是:WebSocket 连接建立后,所有 MQTT 控制包(CONNECT、PUBACK、PINGREQ)都封装在 WebSocket 帧内,但 Broker 日志里看到的仍是原始 MQTT 协议行为。调试时别盯着 WebSocket 握手成功就以为万事大吉——得用 mosquitto_sub -t '$SYS/brokers/+/clients/+' -v 看实际在线 clientid,或抓包确认 PINGRESP 是否按时返回。











