Native asynchronous MQTT 3.1.1 client with QoS 0/1 and clean-session reconnect
moon add Strangelight-Merser/moon-mqtt-clientmoon update
moon check --target native
moon test --target native
moon build --target nativepython3 -m venv .venv
.venv/bin/pip install -r tests/integration/requirements.txt
./scripts/moon.sh build --target native
.venv/bin/python examples/mqtt_demo/demo.py./scripts/moon.sh run examples/mqtt_demo/cli --target native -- \
publish --host 127.0.0.1 -t lab/state -m ON --qos 1 --retain --stats
./scripts/moon.sh run examples/mqtt_demo/cli --target native -- \
subscribe --host 127.0.0.1 -t 'lab/#' --count 1 --timeout-ms 3000async fn main {
let config = @mqtt.Config::new("127.0.0.1", "my-moon-client")
@mqtt.with_client(config, async fn(client) {
let results = client.subscribe([
{ topic: "lab/temperature", qos: @mqtt.AtLeastOnce },
])
if results == [@mqtt.Granted(@mqtt.AtLeastOnce)] {
client.publish("lab/status", @utf8.encode("ready"),
qos=@mqtt.AtLeastOnce, retain=true)
}
// 在这里处理 Connected、Disconnected 和 MessageReceived 事件。
// 长期运行的订阅者必须持续调用 next_event() 消费事件。
})
}| 结果 | 能确认什么 |
|---|---|
| QoS 0 发布正常返回 | 传输写入完成;没有 broker 确认。 |
| QoS 1 发布正常返回 | 本次连接收到匹配的 PUBACK;不能据此确认下游处理完成或设备执行了动作。 |
| NotSent | 请求在开始写入前失败。 |
| OutcomeUnknown | 写入已经开始,但操作结果未确认,包括部分写入和确认丢失。 |
| 请求返回 Backpressure | 发送队列或在途请求达到上限,无法接受本次请求。 |
| 事件队列或控制队列溢出 | 客户端报错并终止连接。 |
| 配置 | 默认值 |
|---|---|
| 业务发送队列 / 控制槽位 / 事件队列 | 64 / 16 / 128 |
| 待完成操作数 / 单个报文大小 | 32 / 65,536 字节 |
| 连接、操作、写入、PINGRESP 超时 | 各 5 秒 |
| keepalive | 30 秒 |
| 连续重连次数 | 最多 10 次 |
| 重连间隔 | 从 250 ms 增长至 5 秒,并加入每个客户端独立的随机抖动 |
PYTHON=.venv/bin/python ./tests/integration/run.sh
./scripts/check.sh# 独立模拟设备的四种状态同步场景
.venv/bin/python examples/mqtt_demo/demo.py
# 从 Mooncakes 安装到全新的临时模块,并完成 QoS 1 消息往返
.venv/bin/python tests/consumer_smoke.py --registry
# 固定版本的官方 EMQX 镜像:收发、拒绝订阅、重启恢复;需要 Docker
./tests/emqx.sh
# 30 分钟重启压力测试:1 KiB QoS 1、16 个并发任务、100 次断线恢复
# broker 默认仅记录警告和错误,日志大小有上限
MOONBIT_ASYNC_CHECK_FD_LEAK=1 .venv/bin/python tests/soak.py \
--duration 1800 --cycles 100 --artifacts tests/integration/artifacts/soakpub(all) suberror ClientError {
InvalidConfig(String)
ProtocolError(String)
NotConnected
Closed
Backpressure(String)
NotSent(String)
OutcomeUnknown(String)
ConnectionRefused(String)
ReconnectExhausted(String)
OperationTimeout(String)
WriteTimeout(String)
PingResponseTimeout(String)
TlsFailure(String)
} derive(Debug)type Clientasync fn Client::subscribe(self : Client, topics : Array[Subscription]) -> Array[SubscriptionResult]pub(all) struct ClientIdentity {
certificate_chain_file : String
private_key_file : String
}pub(all) struct ClientStats {
generation : Int
connected : Bool
business_queue_depth : Int
control_queue_depth : Int
event_queue_depth : Int
pending_requests : Int
reconnects : Int
disconnects : Int
unknown_outcomes : Int
last_disconnect : String?
}pub(all) struct Config {
host : String
port : Int
client_id : String
tls : TlsMode
client_identity : ClientIdentity?
username : String?
password : Bytes?
will : Will?
keep_alive_secs : Int
connect_timeout_ms : Int
operation_timeout_ms : Int
write_timeout_ms : Int
ping_response_timeout_ms : Int
send_capacity : Int
receive_capacity : Int
max_inflight : Int
max_packet_size : Int
reconnect_delay_ms : Int
max_reconnect_delay_ms : Int
reconnect_attempts : Int
control_capacity : Int
}fn Config::new(host : String, client_id : String, port? : Int, tls? : TlsMode, client_identity? : ClientIdentity?, username? : String?, password? : Bytes?, will? : Will?, keep_alive_secs? : Int, connect_timeout_ms? : Int, operation_timeout_ms? : Int, write_timeout_ms? : Int, ping_response_timeout_ms? : Int, send_capacity? : Int, receive_capacity? : Int, max_inflight? : Int, max_packet_size? : Int, reconnect_delay_ms? : Int, max_reconnect_delay_ms? : Int, reconnect_attempts? : Int, control_capacity? : Int) -> ConfigInstall
Download zipNative asynchronous MQTT 3.1.1 client with QoS 0/1 and clean-session reconnect