uniapp小程序里用mqtt.js做即时通讯,踩了7个坑终于跑通了

2026-08-18 0 980

项目需要给小程序加一个“消息通知”功能,后端给了MQTT接入方式。一开始我觉得简单,用WebSocket直接连不就行了,结果真上手的时候,发现小程序和H5不一样,不只是url换个ws://那么简单。中间踩了一堆坑,从连接失败到收发消息乱掉,再到退出页面连接还在后台偷偷跑。今天把整个接入过程写出来,省得大家再绕弯。

一、选型:为什么不用原生WebSocket

后端用的是EMQX,标准MQTT协议。如果用原生WebSocket,需要自己处理MQTT协议中的PUBLISH、SUBSCRIBE、心跳等细节,非常繁琐。所以直接用mqtt.js这个库,它已经封装好了协议。而且它在小程序环境也能跑,但要改一点东西。

网上很多教程让你去npm安装mqtt,然后直接import,但在小程序里直接跑会报错,因为mqtt.js默认依赖Node.js的很多模块(比如process, global)。所以要么用webpack配置alias,要么用在线构建的版本。这里我选择使用npm包,然后通过uniapp的vue.config.js去解决。

二、安装和基础配置

我项目用的是vue3+vite,因此按照vite的方式处理。

npm install mqtt

然后在vue.config.js里做如下配置(vite就用vite.config.js对应的地方):

// vue.config.js 如果你的项目是vue2
const webpack = require('webpack');

module.exports = {
    configureWebpack: {
        plugins: [
            new webpack.ProvidePlugin({
                process: 'process/browser',
                Buffer: ['buffer', 'Buffer']
            })
        ],
        resolve: {
            fallback: {
                "stream": require.resolve("stream-browserify"),
                "crypto": require.resolve("crypto-browserify"),
                "assert": require.resolve("assert/"),
                "http": require.resolve("stream-http"),
                "https": require.resolve("https-browserify"),
                "os": require.resolve("os-browserify/browser"),
                "url": require.resolve("url/"),
                "path": require.resolve("path-browserify")
            }
        }
    }
}

如果你用的是vite,需要在vite.config.js里配置defineresolve.alias等,特别麻烦。我的建议是:如果小程序为主,直接用HBuilderX的“npm支持”编译,它内置的webpack处理方案成熟一点。我就是用HBuilderX运行的。

三、第一个坑:mqtt.js在小程序里连不上

我在H5测试一切正常,在小程序模拟器里连不上,报错WebSocket connection to 'wss://xxxx/mqtt' failed。后来发现是因为小程序要求域名必须是合法配置的wss,而且不能带端口路径。其次mqtt.js默认使用WebSocket的全局对象,小程序里没有原生WebSocket?其实有,但接口有点不一样。最后我找到社区方案:单独适配mqtt.js的websocket入口。

npm包提供mqtt/dist/mqtt.min.js这个UMD版本,里面通过wx.connectSocket实现。不过在uniapp里,最好的方式是用mqtt.min.js并修改它的连接代码。

import mqtt from 'mqtt/dist/mqtt.min.js';

但直接用这个路径,uniapp打包时可能还会遇到global问题。为了省事,我直接在项目里放了一份mqtt.js文件(从unpkg下载的es5版本),然后在页面里用require('../../utils/mqtt.js')引入,确保不经过vite的转译。这个看似笨办法,但确实稳定。

四、连接参数的正确写法

连接时不能和H5一样直接写ws://。小程序里必须用wxs://或者wss://?其实协议前缀还是wss://,但是不区分大小写?我的理解是:后端如果是wss,那路径就是wss://xxxx/mqtt。但需要注意的是,在小程序后台把socket合法域名配置好,不是https那个,是socket合法域名。例如wss://mqtt.example.com,不能带端口。

我的连接参数如下:

const client = mqtt.connect('wss://mqtt.example.com/mqtt', {
    clientId: 'uni_' + Date.now() + '_' + Math.random().toString(16).substr(2),
    username: 'user001',
    password: 'pass123',
    clean: true,
    reconnectPeriod: 4000,
    connectTimeout: 5000,
    protocolVersion: 4
});

这里的clientId必须唯一,不能写死。username和password由后端提供。如果连接不上,八成是clientId冲突或者域名没配上。

五、消息的收发与界面更新

连接成功之后,订阅主题就可以收到消息。在uniapp页面里,需要注意消息回调是在子线程还是主线程?mqtt.js回调默认在同一线程,但在小程序中网络回调需要确保UI更新在Page里,直接在回调里使用this可能不对,可以在外面把this存一下。

onLoad() {
    const that = this;
    this.client.on('connect', () => {
        console.log('连接成功');
        that.client.subscribe('user/123/messages', (err) => {
            if (!err) {
                console.log('订阅成功');
            }
        });
    });

    this.client.on('message', function(topic, message) {
        const data = JSON.parse(message.toString());
        // 注意:这里的this不是页面实例
        that.messageList.push(data);
        console.log('收到消息', data);
    });
}

但是如果你在页面onLoad里创建client,那么每次进入页面都会新建一个连接,离开页面时如果不关闭,连接会一直存在,造成资源浪费。所以通常把mqtt连接放在一个独立的模块里,做成单例。我只在一个不需要全局的页面里测试,所以直接在onLoad创建,onUnload关闭。

六、页面关闭后连接还在后台的坑

一开始我没在onUnload里调用client.end(true),结果退出页面后,控制台还在打印收到消息,而且再次进入页面时会创建新连接,旧连接依然存在,导致重复消息。正确做法是:

onUnload() {
    if (this.client) {
        this.client.end(true); // 表示强制断开
    }
}

但这样也有一个问题:如果小程序切到后台,连接会被系统销毁,那么回来时连不上。需要通过监听onShowonHide来重建连接。更完善的做法是做成全局单例,用App.vue里创建连接,页面中只订阅topic。我这里确实就偷懒了,直接页面管理。

七、掉线重连会导致订阅丢失

mqtt.js自带reconnectPeriod,断线后会自动重连,但重连成功后需要重新订阅之前的所有主题。所以必须在connect回调里再执行subscribe。此外,如果用户网络状态变化,也会触发close或offline事件。我增加一个handler:

this.client.on('reconnect', () => {
    console.log('正在重连...');
});

this.client.on('close', () = > {
    console.log('连接关闭');
});

this.client.on('offline', () => {
    console.log('已离线');
});

其中end()会触发close,所以不要在close里处理重连逻辑,否则会重复。

八、收到的消息比预期多或乱序

MQTT的QoS级别会影响消息可达性。我后端发送时用了QoS1,但我订阅时没设置QoS,默认可能是0,导致消息乱序重复。建议订阅时统一用{ qos: 1 }

client.subscribe('user/123/messages', { qos: 1 }, (err) => {
    // 回调
});

同时发送消息时,如果客户端要发布,推荐使用client.publish(topic, payload, { qos: 1 }),避免消息丢失。

九、完整示例代码(页面内直接可用)

<template>
    <view class="container">
        <input v-model="msg" placeholder="输入消息" />
        <button @click="sendMessage">发送</button>
        <view v-for="(item, index) in messages" :key="index">{{ item }}</view>
    </view>
</template>

<script>
import mqtt from '../../utils/mqtt.min.js';

export default {
    data() {
        return {
            client: null,
            msg: '',
            messages: []
        };
    },
    onLoad() {
        this.connectMqtt();
    },
    onUnload() {
        if (this.client) {
            this.client.end(true);
        }
    },
    methods: {
        connectMqtt() {
            this.client = mqtt.connect('wss://mqtt.example.com/mqtt', {
                clientId: 'uni_' + Date.now(),
                username: 'test',
                password: 'secret',
                clean: true,
                reconnectPeriod: 5000,
                connectTimeout: 5000,
                protocolVersion: 4
            });

            this.client.on('connect', () => {
                console.log('连接成功');
                this.client.subscribe('order/notify', { qos: 1 }, (err) => {
                    if (err) {
                        console.error('订阅失败', err);
                    }
                });
            });

            this.client.on('message', (topic, payload) => {
                const text = payload.toString();
                this.messages.push(topic + ': ' + text);
            });

            this.client.on('reconnect', () => {
                console.log('重连中');
            });

            this.client.on('error', (err) => {
                console.error('MQTT错误', err);
            });
        },
        sendMessage() {
            if (!this.msg) return;
            this.client.publish('order/notify', this.msg, { qos: 1 });
            this.messages.push('我发送: ' + this.msg);
            this.msg = '';
        }
    }
};
</script>

十、一些真正的坑总结

  • HBuilderX内置webpack可以解决mqtt.js的全局问题,但最好不要用npm安装的,直接用本地min.js。
  • 小程序后台域名必须配置socket合法域名,而且必须是https/wss,不能用ip加端口。
  • 连接成功回调里订阅,不然重连后不会自动订阅。
  • 离开页面要end(true),否则连接泄漏。
  • 如果要跨页面共享消息,需要把mqtt连接挂在Vuex或全局,页面只负责subscribe和onMessage分发。
  • 消息到达后要判断页面是否处于前台,如果退到后台,不要弹UI,可以改用全局变量或本地存储。
  • 在部分安卓手机上,长时间连接会被系统回收,所以最好监听App的onShow/onHide手动重连。

十一、能不能用uniapp提供的uni.connectSocket去封装?

可以,但那样就相当于自己实现MQTT协议了,太麻烦。除非你只需要极简功能,否则还是用mqtt.js成熟。

十二、最终建议

如果你的业务只需要服务端消息推送,不需要客户端上报,可以考虑使用uni.subscribeMessage或微信公众号模板消息,小程序原生支持,还不需要个人服务器。但如果要做IM双向聊天,那MQTT就是很好的选择。我在这个项目里只用了订阅和发布,已经够了。

上面这些坑是我分别在小程序开发工具、真机调试和H5端测试总结出来的,希望能帮你省点时间。

uniapp小程序里用mqtt.js做即时通讯,踩了7个坑终于跑通了
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请发送邮件1506151422@qq.com联系,将会及时下架删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 uniapp uniapp小程序里用mqtt.js做即时通讯,踩了7个坑终于跑通了 https://www.taomawang.com/web/uniapp/2564.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务