【aardio】零依赖纯 TCP 快速搞定 MQTT 消息发布调试

【aardio】不到百行代码,零依赖纯 TCP 快速搞定 MQTT 消息发布调试

在进行工业控制、物联网(IoT)开发或者自动化测试时,我们经常需要快速向 MQTT 服务器发送一条指令来触发设备动作。虽然市面上有很多功能完整的 MQTT 库,但对于临时调试、发送单条指令的场景来说,引入庞大的第三方库或配置复杂的环境显得有些大材小用。

本文分享一段用 aardio 实现的硬核脚本。它直接基于纯 TCP 客户端(wsock.tcp.client),在代码中手动完成了 MQTT 二进制报文的拼装与解析。不到 100 行代码,实现全绿色、零依赖、即开即用的 MQTT QoS 0 消息发布。

注意本文代码由 AI 自动生成,经过测试非常好用。


🚀 核心优势

  • 零外部依赖:仅使用 aardio 自带的内置库,拷贝代码即可直接运行。
  • 超轻量高效:没有复杂的封装,直面 MQTT 协议底层,内存占用极小。
  • 非常适合调试:在工业控制台体调试、自动化脚本或者临时交互时,可作为“微型瑞士军刀”使用。

💾 完整实现代码

你可以直接将以下代码复制到 aardio 编辑器中,修改底部的服务器 IP、主题(Topic)和负载(Payload)即可直接运行:

import console;
import wsock.tcp.client;

// ==========================================
// ====== MQTT 协议底层报文辅助函数 ======
// ==========================================

// 1. 剩余长度编码(MQTT 变长编码:每字节低7位存数据,最高位1表示后续还有字节)
var encodeRemainingLength = function(length){
    var buf = raw.buffer(4);
    var idx = 0;
    while(true){
        var b = length % 128;
        length = length >> 7;
        if(length > 0) b = b | 0x80; // 最高位置 1
        buf[idx+1] = b;
        idx = idx + 1;
        if(length <= 0) break;
    }
    return tostring(raw.slice(buf, 1, idx));
}

// 2. 大端序 2 字节长度前缀 + 字符串(MQTT 字符串标准格式)
var packLenStr = function(str){
    var len = #str;
    return string.pack((len >> 8) & 0xFF, len & 0xFF) ++ str;
}

// 3. 构建 MQTT CONNECT 控制报文
var buildConnect = function(clientId){
    // 可变报头:协议名长度+协议名("MQTT") + 协议级别(4) + 连接标志(0x02 Clean Session) + 保持连接时间(60s)
    var varHeader = packLenStr("MQTT") 
        ++ string.pack(4, 0x02) 
        ++ string.pack((60 >> 8) & 0xFF, 60 & 0xFF);
    
    // 有效载荷:客户端 ID
    var remaining = varHeader ++ packLenStr(clientId);
    
    // 固定报头:0x10 (CONNECT 类型) + 剩余长度 + 载荷内容
    return string.pack(0x10) ++ encodeRemainingLength(#remaining) ++ remaining;
}

// 4. 构建 MQTT PUBLISH 控制报文 (QoS 0)
var buildPublish = function(topic, message){
    // 载荷内容:主题名 + 消息正文
    var remaining = packLenStr(topic) ++ message;
    
    // 固定报头:0x30 (PUBLISH QoS 0) + 剩余长度 + 载荷内容
    return string.pack(0x30) ++ encodeRemainingLength(#remaining) ++ remaining;
}

// ==========================================
// ====== 核心功能:全流程发布消息 ======
// ==========================================
var mqttPublish = function(host, port, topic, payload){
    // 自动生成基于时间戳的唯一 ClientID
    var clientId = "aardio-" ++ tostring(time.tick());

    var tcp = wsock.tcp.client();
    tcp.setTimeouts(5000, 5000); // 设置发送与接收超时

    // 【步骤 1】建立物理 TCP 连接
    var ok, err = tcp.connectTimeout(host, port, 5);
    if(!ok) return null, "连接失败: " + (err || "未知错误");

    // 【步骤 2】发送 CONNECT 报文并等待握手响应 (CONNACK)
    tcp.write(buildConnect(clientId));
    var connack = tcp.readEx(4); // CONNACK 固定为 4 字节
    if(!connack || #connack < 4){
        tcp.close();
        return null, "未收到响应或连接被代理拒绝 (CONNACK 为空)";
    }

    // 解析返回码 (第 4 字节为连接返回码,0 表示接受连接)
    var retCode = connack[4];
    if(retCode != 0){
        tcp.close();
        return null, "MQTT 连接失败,服务器返回码: " + retCode;
    }

    // 【步骤 3】发送 PUBLISH 报文 (QoS 0 不需要等待服务器应答)
    tcp.write(buildPublish(topic, payload));

    // 【步骤 4】发送 DISCONNECT 断开连接报文 (优雅退出)
    thread.delay(200);             // 稍微等待,确保发布缓冲区数据已送出
    tcp.write(string.pack(0xE0, 0x00)); // 0xE0 表示 DISCONNECT 报文
    thread.delay(100);             // 留给服务器处理关闭的时间
    tcp.close();

    return true;
}

// ==========================================
// ====== 运行与调试示例 ======
// ==========================================
console.open();

var brokerIp = "192.168.97.34"; // 你的 MQTT Broker 地址
var port = 1883;
var topic = "lua/cmd/run";
var jsonPayload = '{"fn": "voltage_all_phase_test.lua"}';

print("正在尝试发布消息...");
var ok, err = mqttPublish(brokerIp, port, topic, jsonPayload);

if(ok) {
    print("【成功】MQTT 消息已成功送达 Broker!");
} else {
    print("【失败】原因:", err);
}

console.pause();


🛠️ 代码技术点解析

对于对底层协议感兴趣的同学,这段代码有几个精妙的细节值得学习:

1. 变长长度编码(encodeRemainingLength)

MQTT 协议为了节省网络带宽,其“剩余长度”字段采用了可变长度的编码方式(类似于一种压缩算法)。每个字节的最高位(bit 7)作为标志位,如果为 1,代表后面还有一个字节参与表示长度;如果为 0,则代表这是长度字段的最后一个字节。
代码中利用 length % 128 和位移操作 length >> 7 完美手工实现了这一解析逻辑。

2. 位操作与字符串组装

MQTT 报文中的双字节整数(如字符串长度、KeepAlive 时间)均要求大端序(Big-Endian)传输。
代码中利用 (len >> 8) & 0xFF 取出高 8 位,len & 0xFF 取出低 8 位,再利用 aardio 独具特色的字符串连接符 ++ 直接将字节与原始字符串高速拼接,效率极高。

3. 优雅下线

在 PUBLISH 发送完毕后,代码并未粗暴地直接关闭 TCP 连接,而是发送了 0xE0, 0x00(即 MQTT 的 DISCONNECT 报文)。这样做可以明确告知 MQTT Broker 客户端是主动正常下线,避免 Broker 将其误判为异常断开而触发遗嘱消息(Will Message)。


💡 适用场景与局限性

  • 适用:发送频率低、单次单条的控制指令下发、轻量化上位机测试工具、无需接收订阅消息的单向上报。
  • 局限:本代码针对“快发快取”的调试场景设计,仅支持 QoS 0(最多交付一次)。如果你的业务场景需要高可靠性的断线重连、长时间维持长连接心跳(KeepAlive PING)、或者复杂的 QoS 1/2 消息应答与主题订阅,建议在正式项目中引入更完整的工业级 MQTT 客户端组件。
posted @ 2026-05-22 17:27  口嗨养生博  阅读(4)  评论(0)    收藏  举报