mqttsn网关

环境

  • EMQX 版本:5.8.9
  • 操作系统版本:Alibaba Cloud Linux 3 (Soaring Falcon)

重现此问题的步骤

  1. 启用Mqttsn网关,配置挂载点:mqttsn/${clientid}/
  2. 终端连接emqx,ClearSession=false,并订阅mqttsn/设备sn/downlink 主题,Qos=1,然后断开连接,网关中客户端状态显示未连接
  3. 应用端下发消息到此主题,Qos=1
  4. 终端再次连接并订阅,Qos=1,始终无法收到应用端下发的消息


预期行为

终端可以收到此消息。

实际行为

一直收不到消息

问题2:

  1. 终端连接emqx,ClearSession=false,并订阅mqttsn/设备sn/downlink 主题,Qos=1,不断开,网关中客户端状态显示已连接
  2. 应用端下发消息到此主题,Qos=1
  3. 终端再次连接并订阅,Qos=1,收到消息,不回复Ack,服务端不重复下发

相关跟踪日志见附件:
ByTopic.zip (632 字节)
PS8J84.zip (4.3 KB)

日志中包含以下错误信息,不知道是否以上两个问题的原因:
2026-08-02T15:20:37.477916+08:00 [error] crasher: initial call: emqx_gateway_conn:init/6, pid: <0.4139.0>, registered_name: , exit: {#{context => function_clause,stacktrace => [{emqx_mqttsn_channel,handle_in,[{mqtt_sn_message,4,{{mqtt_sn_flags,false,0,false,false,false,0},1,60,<<“PS8J84”>>}},{channel,#{cm => <0.3845.0>,gwname => mqttsn},1,false,#{peername => {{116,206,209,187},49153},sockname => {{0,0,0,0},1884},keepalive => 60,peercert => nossl,clientid => <<“PS8J84”>>,proto_ver => <<“1.2”>>,conn_mod => emqx_gateway_conn,connected_at => 1785654635341,proto_name => <<“MQTT-SN”>>,socktype => udp,clean_start => false,expiry_interval => 7200000,disconnected_at => 1785654746955},#{peername => {{116,206,209,187},49153},protocol => ‘mqtt-sn’,username => undefined,listener => ‘mqttsn:udp:default’,clientid => <<“PS8J84”>>,peerhost => {116,206,209,187},enable_authn => true,is_superuser => false,is_bridge => false,zone => default,mountpoint => <<“mqttsn/PS8J84/”>>,sockport => 1884,auth_expire_at => undefined},#{registry => #{name_to_id => #{},last_topic_id => 1024,id_to_name => #{}},session => {session,<<“PS8J84”>>,<<0,6,88,11,30,59,40,189,244,69,0,0,16,43,0,1>>,false,#{<<“mqttsn/PS8J84/downlink”>> => #{nl => 0,is_new => true,qos => 1,rap => 0,rh => 1}},infinity,false,{inflight,1,{0,nil}},{mqueue,true,1000,0,0,disabled,0,{queue,,,0},{shift_opts,10,0},undefined,undefined},1,infinity,#{},100,300000,1785654635342}},{keepalive,30000,3,undefined,30000,90000},undefined,#{clientid => <<“${ConnInfo.clientid}”>>},disconnected,undefined,,undefined,#{keepalive => #Ref<0.994011145.1961099265.41498>,expire_session => #Ref<0.994011145.1961099265.43502>},false,false,}],[{file,“emqx_mqttsn_channel.erl”},{line,451}]},{emqx_gateway_conn,with_channel,3,[{file,“emqx_gateway_conn.erl”},{line,772}]},{emqx_gateway_conn,process_msg,2,[{file,“emqx_gateway_conn.erl”},{line,427}]},{emqx_gateway_conn,process_msg,2,[{file,“emqx_gateway_conn.erl”},{line,433}]},{emqx_gateway_conn,handle_recv,3,[{file,“emqx_gateway_conn.erl”},{line,390}]},{proc_lib,wake_up,3,[{file,“proc_lib.erl”},{line,251}]}],exception => error},[{emqx_gateway_conn,terminate,2,[{file,“emqx_gateway_conn.erl”},{line,615}]},{proc_lib,wake_up,3,[{file,“proc_lib.erl”},{line,251}]}]}, ancestors: [<0.3898.0>,esockd_sup,<0.2446.0>], message_queue_len: 0, messages: , links: [<0.3898.0>], dictionary: [{outgoing_bytes,20},{‘$logger_metadata$’,#{peername => “116.206.209.187:49153”,clientid => <<“PS8J84”>>}},{send_pkt,4},{incoming_bytes,56},{{#{retain => false,action_type => publish,qos => 1},<<“real_data”>>},{allow,1785654676373}},{recv_pkt,5},{authz_cache_size,2},{recv_msg,1},{{#{action_type => subscribe,qos => 1},<<“downlink”>>},{allow,1785654650933}},{authz_keys_q,{[{#{retain => false,action_type => publish,qos => 1},<<“real_data”>>}],[{#{action_type => subscribe,qos => 1},<<“downlink”>>}]}},{guid,{1785654746955992,268577189924907,3}},{incoming_pubs,1}], trap_exit: false, status: running, heap_size: 987, stack_size: 28, reductions: 12034; neighbours:

附件里的应用端实际发布的是 QoS 0,不是 QoS 1。

ByTopic 跟踪在 15:26:04 和 15:37:11 都显示:

action: PUBLISH(Q0,R0)
topic: mqttsn/PS8J84/downlink

订阅 QoS 1 不会把发布端 QoS 0 升级成 QoS 1,最终投递仍是 QoS 0。因此这两次下发不能用来验证离线 QoS 1 会话恢复;在线收到消息后也不需要 PUBACK,自然不会触发 QoS 1 重发。

先把 MQTTX 的发布 QoS 改成 1,确认跟踪中出现 PUBLISH(Q1,R0),再分别复现。离线测试保持相同 Client ID 和 CleanSession=false,重连后先不要重复 SUBSCRIBE,直接检查原订阅和排队消息是否恢复。

另外,function_clause 是已知问题,不需要再新提 issue。这个日志和 EMQX #17796#17815 是一样的:CleanSession=false 的 MQTT-SN 客户端 DISCONNECT 后,旧 channel 保持 disconnected;相同 Client ID 从同一 UDP source tuple 再发 CONNECT 时,5.8.9 没有对应的 handle_in 分支而崩溃。#17815 已增加 t_disconnected_same_proxy_same_clientid_reconnect 回归用例,验证重连会接管旧会话。

修复已合并到 6.0/6.1/6.2 维护分支,计划从 6.0.4 发布;截至 2026-08-02,已发布的 6.0.3、6.1.3、6.2.2 都还不包含完整修复,5.8.9 也不包含。当前先用发布端 QoS 1 验证第一个问题;重连崩溃需要等包含 #17796#17815 的正式版本。

PS8J84 (2).zip (28.2 KB)
ByTopic (2).zip (1.5 KB)
这是最新的完整日志,都是使用Qos=1。
所以function_clause 不是引起这两个问题的原因,是吗。

就是这个 function_clause 引起的,不过在高版本已经修复了。

测试了6.2.2版本,还是有这个异常,请问是哪个版本修复了。如果没有,大概什么时候修复呢
2026-08-03T09:26:19.388229+08:00 [MULTI_TENANCY] PS8J84@116.206.209.187:49153 msg: no_tenant_namespace,

2026-08-03T09:26:19.388390+08:00 [AUTHN] PS8J84@116.206.209.187:49153 msg: authentication_result, payload_encode: hex, reason: no_chain, result: [Encoded(hex)=[…(7 bytes)]]

2026-08-03T09:26:39.521778+08:00 [AUTHZ] PS8J84@116.206.209.187:49153 msg: authorization_module_ignore, action: SUBSCRIBE(Q1), authorize_type: client_info, module: emqx_authz_client_info, topic: downlink, username: undefined

2026-08-03T09:26:39.521925+08:00 [AUTHZ] PS8J84@116.206.209.187:49153 msg: authorization_matched_allow, action: SUBSCRIBE(Q1), authorize_type: file, module: emqx_authz_file, topic: downlink, username: undefined

2026-08-03T09:26:39.522090+08:00 [SUBSCRIBE] PS8J84@116.206.209.187:49153 msg: subscribe, sub_id: PS8J84, sub_opts: [nl: 0, is_new: true, qos: 1, rap: 0, rh: 1], topic: mqttsn/PS8J84/downlink

2026-08-03T09:27:10.394391+08:00 [AUTHZ] PS8J84@116.206.209.187:49153 msg: authorization_module_ignore, action: PUBLISH(Q1,R0), authorize_type: client_info, module: emqx_authz_client_info, topic: real_data, username: undefined

2026-08-03T09:27:10.394527+08:00 [AUTHZ] PS8J84@116.206.209.187:49153 msg: authorization_matched_allow, action: PUBLISH(Q1,R0), authorize_type: file, module: emqx_authz_file, topic: real_data, username: undefined

2026-08-03T09:27:10.394653+08:00 [PUBLISH] PS8J84@116.206.209.187:49153 msg: publish_to, payload_encode: hex, topic: mqttsn/PS8J84/real_data, payload: […(16 bytes)]

2026-08-03T09:34:39.149739+08:00 [error] crasher: initial call: emqx_gateway_conn:init/6, pid: <0.6841.0>, registered_name: , process_label: {clientid,<<“PS8J84”>>}, exit: {#{context => function_clause,stacktrace => [{emqx_mqttsn_channel,handle_in,[{mqtt_sn_message,4,{{mqtt_sn_flags,false,0,false,false,false,0},1,60,<<“PS8J84”>>}},{channel,#{cm => <0.6027.0>,gwname => mqttsn,metrics_tab => emqx_gateway_mqttsn_metrics},1,false,#{peername => {{116,206,209,187},49153},sockname => {{0,0,0,0},1884},keepalive => 60,socktype => udp,peercert => nossl,proto_ver => <<“1.2”>>,clientid => <<“PS8J84”>>,conn_mod => emqx_gateway_conn,connected_at => 1785720379388,proto_name => <<“MQTT-SN”>>,clean_start => false,expiry_interval => 7200000,disconnected_at => 1785720460848},#{peername => {{116,206,209,187},49153},protocol => ‘mqtt-sn’,username => undefined,peerhost => {116,206,209,187},listener => ‘mqttsn:udp:default’,enable_authn => true,mountpoint => <<“mqttsn/PS8J84/”>>,is_superuser => false,clientid => <<“PS8J84”>>,is_bridge => false,zone => default,sockport => 1884,auth_expire_at => undefined},#{session => {session,<<“PS8J84”>>,<<0,6,88,26,108,225,180,5,244,69,0,0,26,185,0,1>>,false,#{<<“mqttsn/PS8J84/downlink”>> => #{nl => 0,is_new => true,qos => 1,rap => 0,rh => 1}},infinity,false,{inflight,1,{0,nil}},{mqueue,true,1000,0,0,disabled,0,{queue,,,0},{shift_opts,10,0},undefined,undefined},1,infinity,#{},100,300000,1785720379388},registry => #{name_to_id => #{},last_topic_id => 1024,id_to_name => #{}}},{keepalive,30000,3,undefined,0,90000},undefined,#{clientid => <<“${ConnInfo.clientid}”>>},disconnected,undefined,,undefined,#{keepalive => #Ref<0.2835727685.524288001.191445>,expire_session => #Ref<0.2835727685.524288001.192769>},false,false,}],[{file,“emqx_mqttsn_channel.erl”},{line,441}]},{emqx_gateway_conn,with_channel,3,[{file,“emqx_gateway_conn.erl”},{line,776}]},{emqx_gateway_conn,process_msg,2,[{file,“emqx_gateway_conn.erl”},{line,425}]},{emqx_gateway_conn,process_msg,2,[{file,“emqx_gateway_conn.erl”},{line,431}]},{emqx_gateway_conn,handle_recv,3,[{file,“emqx_gateway_conn.erl”},{line,388}]},{proc_lib,wake_up,3,[{file,“proc_lib.erl”},{line,344}]}],exception => error},[{emqx_gateway_conn,terminate,2,[{file,“emqx_gateway_conn.erl”},{line,613}]},{proc_lib,wake_up,3,[{file,“proc_lib.erl”},{line,344}]}]}, ancestors: [<0.6048.0>,esockd_sup,<0.4214.0>], message_queue_len: 0, messages: , links: [<0.6048.0>], dictionary: [{extsub_st,{st,{registry,{buffer,0,{0,nil},#{}},#{},#{},},undefined,#{},#{}}},{outgoing_bytes,20},{send_pkt,4},{incoming_pubs,1},{recv_pkt,5},{incoming_bytes,56},{{emqx_gateway_conn,packet_metric_name,received,subscribe},‘packets.subscribe.received’},{‘$logger_metadata$’,#{peername => “116.206.209.187:49153”,clientid => <<“PS8J84”>>}},{authz_cache_size,1},{{emqx_gateway_conn,packet_metric_name,sent,connack},‘packets.connack.sent’},{{emqx_gateway_conn,packet_metric_name,received,disconnect},‘packets.disconnect.received’},{{emqx_gateway_conn,packet_metric_name,sent,disconnect},‘packets.disconnect.sent’},{extsub_channel_info,#{can_receive_acks => false,conninfo_fn => #Fun<emqx_gateway_cm.4.47216938>}},{authz_keys_q,{[{emqx_authz_cache,<<“real_data”>>,1,0}],}},{{emqx_gateway_conn,packet_metric_name,sent,puback},‘packets.puback.sent’},{{emqx_gateway_conn,packet_metric_name,sent,suback},‘packets.suback.sent’},{guid,{1785720460848842,268577189927609,3}},{{emqx_authz_cache,<<“real_data”>>,1,0},{allow,1785720430394}},{recv_msg,1},{{emqx_gateway_conn,packet_metric_name,received,connect},‘packets.connect.received’},{{emqx_gateway_conn,packet_metric_name,received,publish},‘packets.publish.received’},{{emqx_gateway_metrics,tabname,mqttsn},emqx_gateway_mqttsn_metrics}], trap_exit: false, status: running, heap_size: 987, stack_size: 29, reductions: 14172; neighbours:

2026-08-03T09:35:22.497225+08:00 [MULTI_TENANCY] PS8J84@116.206.209.187:49154 msg: no_tenant_namespace,

2026-08-03T09:35:22.497388+08:00 [AUTHN] PS8J84@116.206.209.187:49154 msg: authentication_result, payload_encode: hex, reason: no_chain, result: [Encoded(hex)=[…(7 bytes)]]

6.2.2 还没包含完整修复。 7 月 3 日发布,修复 MQTT-SN UDP proxy/ClientId 路由的 #17815 是 7 月 20 日才合并,所以在 6.2.2 上复现是符合预期的。
当前还没有已经发布、同时包含 #17796#17815 的正式版本。两个修复已经进入 release-60、release-61、release-62 维护分支;PR 的发布版本标记是 6.0.4。6.1/6.2 需要等各自下一次补丁版本。
目前没有可确认的公开发布日期。