设备端mqtt sign(签名)原理以及实践示例

简介: 设备端mqtt sign(签名)原理以及实践示例

前言


背景介绍:由于各种原因,阿里云用户有的设备不能使用Link SDK来接入物联网平台,需要自己编写代码实现mqtt sign,本文将初步的介绍一下签名的原理以及提供几个用代码实现mqtt sign的示例。


基本概念和原理:


本文调试用的工具地址: 调试工具


1、 加密算法:


1-1 散列算法(哈希、摘要)

也叫做Hash函数,常见的散列函数有MD5、SHA-1、SHA256等。

通过散列函数,可以为数据创建“数字指纹”(散列值、数字摘要)。散列值通常是一个短的随机字母和数字组成的字符串。信息收发双方在通信前商定了具体的散列算法,并且该算法是公开的。如果消息在传递过程中被篡改,则该消息不能与已获得的数字指纹相匹配。


注意:散列函数的主要作用不是完成数据加密和解密的工作,是用来验证数据的完整性的重要技术。



应用场景:A->B,B要验证A发送的内容没有被篡改

A:

原文1:123456

通过哈希函数(如:MD5)生成指纹1:e10adc3949ba59abbe56e057f20f883e


B:

收到原文2:1234567 (假设原文内容被中间人更改了)

把原文2通过MD5生成指纹2:fcea920f7412b5da7be0cf42b8c93759

通过对比指纹1和指纹2来是否一样,来判断原文内容是否被修改,而不是把指纹2逆向解析出原文(MD5不可逆)


特点

单向不可逆只能从输入推导出输出,而不能从输出计算出输入

可破解(不安全)

①、可以反查询

②、不可抵御中间人的攻击

高效,固定长度散列算法的执行效率要尽量高效,针对较长的文本,也能快速地计算出散列值。散列函数通过换算可以将任何长度的数据输出成固定长度的散列值。

(MD5 是128bit -16个字节-32个16进制的字符。)

雪崩效应:如果数据在传输过程中哪怕发生了一比特的修改,经过散列函数运算后,散列值会发生很大的变化。

1-2 HMAC

HMAC算法是基于信息散列算法的基础上,增加了一个密钥对散列算法的结果(指纹)进行加密得到密文1,

接收方也将收到的消息内容,通过同一个密钥对消息内容进行HAMC得到密文2,来比对密文1和密文2是否相同。在散列算法的基础上,增加了一个身份识别的功能。

常见的HMAC算法有:hmacmd5,hmacsha1和hmacsha256,对应散列算法中的md5,sha-1,sha-256。


优点:

优化了散列算法无法抵御中间人攻击的缺点。

假设有 A 、B、C 三个人。

业务场景是A给B要发送一段文字:"I Love You"

单纯使用散列算法的情况:

A发送给B:

原文1:I Love You

MD5指纹1:cd2824bba5d2803793c0553c8f7c0bd1

这个时候C从中作梗,给B发送

C发送给B

原文2:I Hate You

MD5指纹2:03a161dc954eb252606b24121b9ba801

B:收到原文2,通过原文2进行MD5得到指纹3,与指纹2进行比对发现内容完整

得到未被篡改的消息内容【I Hate You】

使用HMAC算法的情况:

A发送给B:

原文1:I Love You

将原文1用密钥1314520

进行HMAC运算得到密文1(签名1):5decea54c0a2f2e2a42f630fb90a6cfd

这个时候C想从中作梗,给B发送

C发送给B

原文2:I Hate You

但是由于没有密钥1314520,使用了其他密钥3344520

进行HMAC运算得到密文2(签名2):eeebed42f8a68907b3d9924c42d82749

B:收到原文2,通过密钥1314520进行HMAC运算

得到密文3(签名3):4aca2934ac46b546a5365f673f272c4c

通过比对密文2和密文3 不匹配,发现消息内容被篡改,成功抵御中间人C的攻击。

缺点:

1、发送方和接收方都要知道这个密钥,上文中的1314520

2、无法证明发送方是自己本人,密钥K如果泄露给了别人,那别人就可以使用密钥K与接收方进行正常通信

HMAC可以增加身份识别,但是存在身份可靠性问题,无法验证该身份是否合法。

(如何解决:CA机构+TLS证书,相当于【公安机关+身份证】。本文不作过多讲解。)


1-3 数字签名

数字签名就是在散列算法的基础上,使用一定的加密算法,进行加密得到别人无法伪造的一段数字串。

1-2中的HMAC也属于数字签名的一种运用,另外像TLS证书上的签名等等也是数字签名的运用,只不过TLS证书

使用的是非对称加密方式,通过公钥解密进行验签,HMAC使用的是对称加密方式,通过比对签名的方式进行验签。

用户A用私钥将原文打上自己的签名,用户B通过用户A发布到网上的公钥(证书)对签名进行解密(验签)来确定用户A的身份以及消息内容是否被更改。


2、MQTT签名机制


2-1概述

mqtt签名实际就是mqtt设备接入(包括一机一密认证、一型一密预注册认证、一型一密预注册、一型一密免预注册)物联网平台CONNECT报文中的mqttPassword这个参数,也就是mqtt接入的密码,物联网平台用来校验设备的身份。


另外,注意,一型一密免预注册添加的设备进行认证,不需要签名,直接使用的是token进行认证

https://help.aliyun.com/document_detail/73742.htm?spm=a2c4g.11186623.0.0.50adee99EgHirS#concept-mhv-ghm-b2b


从文档中可以看到,这里的签名方法使用的是HMAC的方式

signmethod:表示签名算法类型。支持hmacmd5,hmacsha1和hmacsha256,默认为hmacmd5。

结合1-2章节HMAC,mqtt签名机制,实际上就是:设备端将连接报文中的一部分参数当成消息内容,通过HMAC算法

签名生成一个密码,服务器端从收到的报文按照约定也通过HMAC算法签名生成一个密码,进行校验。


2-2设备端(客户端)

注意:mqttClientId和clientId以及ClientID的区别,clientId是mqttClientId的一部分,clientId=ClientID

clientId可以是自定义的,ClientID是由物联网平台提供(一型一密免预注册)。

设备端通过发送MQTT连接报文接入物联网平台,包含三个参数:

(1)、mqttClientId:

认证

注册

控制台注册/
云端API注册设备

免预注册方式添加设备

预注册

免预注册

一机一密

老版公共实例

clientId+"|securemode=3,
signmethod=hmacsha1,timestamp=xxxxx|"

不支持

不支持

新版公共实例

企业实例

一型一密

老版公共实例

clientId+"|securemode=-2,
authType=connwl|"

(这里的clientId非自定义了,整个mqttClientID都是通过免预注册一起返回来的)

clientId+"|securemode=2,
authType=register,random=xxxx,signmethod=xxxx|"

clientId+"|securemode=-2,
authType=regnwl,random=xxxx,signmethod=xxxx|"

新版公共实例

clientId+"|securemode=2,
authType=register,random=xxxx,signmethod=xxxx,instanceId=xxxx|"

clientId+"|securemode=-2,
authType=regnwl,random=xxxx,signmethod=xxxx,instanceId=xxxx|"

企业实例

(2)、mqttUsername:

deviceName+"&"+productKey

(3)、mqttPassword:

一机一密认证:

productKey、deviceName、timestamp和clientId几个参数生成content

一型一密:

将deviceName、productKey、random 几个参数生成content

使用密钥(DeviceSecret或者ProductSecret)进行HMAC 算法签名得到mqttPassword

一机一密用DeviceSecret,一型一密用ProductSecret。

顺便区分一下一机一密和一型一密:

一机一密,是用于激活设备,创建和物联网平台的mqtt通信通道,

一型一密,是用于注册设备,获取设备的接入信息(设备三元组或者ClientID、DeviceToken

一型一密分为预注册和免预注册,预注册的方式得到的是三元组信息,再通过一机一密的方式激活设备。

免预注册的方式得到的是ProductKey、DeviceName、ClientID、DeviceToken,直接通过这几个参数接入物联网平台。

(DeviceToken 作为mqttPassword,所以设备端不需要再计算mqttPassword

2-3物联网平台(服务器)

物联网平台从MQTT报文中的(productKey、deviceName、timestamp和clientId)或者(deviceName、productKey、random)几个参数生成content

从MQTT报文中的signmethod参数,得到HMAC算法,

通过该算法计算签名得到sign来和MQTT报文中的mqttPassword参数进行比对,达到设备身份校验的目的。


2-4如何规避HAMC的缺点

1-2中提到,HMAC,双方都需要"提前”知道密钥,虽然可以校验身份,但是对发送方的身份合法性不能进行校验。


(1)所以设备密钥(DeviceSecret),产品密钥(ProductSecret)这个东西不要泄露给其他人,文档中也有明确提示。只有设备端和平台“知道”,就保证了身份的合法性。

(2)设备和物联网平台建立连接的时候也可以选择TLS接入,即MQTT-TLS,利用TLS证书加强对身份合法性的校验,本文不过多描述。



实践示例


1、使用阿里云现有工具


参考链接,不过多叙述

如何计算MQTT签名参数


2、使用C语言进行mqtt sign。


2-1 概述-准备工作

(1)环境说明

  • 开发语言:C99标准的C语言。
  • 开发工具:Linux 或者Mac OS ,VIM编辑器
  • 编译工具:GCC


(2)前提条件

已在物联网平台控制台,对应实例下,创建产品和设备,并获取MQTT接入域名和设备证书信息(ProductKey、DeviceName和DeviceSerect)。具体操作,请参见:


(3)源码获取

单击打开aiot_mqtt_sign.c复制源码,然后粘贴保存为本地的aiot_mqtt_sign.c文件。aiot_mqtt_sign.c文件定义了函数aiotMqttSign()。

  • 原型:intaiotMqttSign(constchar *productKey, constchar *deviceName, constchar *deviceSecret,  char clientId[150], char username[65], char password[65]);


2-2 修改源码,自定义设备的mqtt接入参数

   (1) 增加stdint.h头文件的包含

           里面有uint32_t 这种数据类型的定义。


(2)编写main函数(程序入口)

int main()

{

  /* invoke aiotMqttSign to generate mqtt connect parameters */

  char clientId[150] = {0};

  char username[65] = {0};

  char password[65] = {0};

  int rc = 0;

#if MQTTAUTH

  /*一机一密*/

  if ((rc = aiotMqttSign(EXAMPLE_PRODUCT_KEY, EXAMPLE_DEVICE_NAME, EXAMPLE_DEVICE_SECRET,   clientId, username, password) < 0)) {

      printf("aiotMqttSign -%0x4x\n", -rc);

      return -1;

  }  

#else

  /*一型一密*/

  if ((rc = aiotMqttSign(EXAMPLE_PRODUCT_KEY, EXAMPLE_DEVICE_NAME, EXAMPLE_PRODUCT_SECRET, clientId, username, password) < 0)) {

      printf("aiotMqttSign -%0x4x\n", -rc);

      return -1;

  }

#endif

  printf("mqttClientId: %s\n", clientId);

  printf("mqttUsername: %s\n", username);

  printf("mqttPassword: %s\n", password);

  return  rc;  

}


(3)增加宏定义 MQTTAUTH、REGISTER、REGNWL

/*mqtt 认证*/

#define MQTTAUTH

/*一型一密预注册*/

#define REGISTER

/*一型一密预注册*/

#define REGNWL


(4)增加宏定义EXAMPLE_PRODUCT_KEY 和 EXAMPLE_DEVICE_CLIENTID

#define EXAMPLE_PRODUCT_KEY "a16hDZJpRCl"

#define EXAMPLE_DEVICE_CLIENTID "qwert"


(5)  删除这两行代码


2-2-1 一机一密

注释掉REGISTER、REGNWL宏,留下MQTTAUTH


 ② 初始化content、mqttClientid所需要的值

(一机一密content要用到productKey、deviceName、timestamp和clientId)。

         ProductKey、DeviceName、DeviceSecret、timestamp。

#ifdef MQTTAUTH

/*一机一密认证/控制台(API)添加的设备的认证*/

#define EXAMPLE_DEVICE_NAME "IoTDeviceDemo1"        

#define EXAMPLE_DEVICE_SECRET "a3b15a116aec5**马赛克**39b7bd4bc4952"

#define TIMESTAMP_VALUE "1655198877533"//当前时间

#define MQTT_CLINETID_KV "|timestamp=1655198877533,_v=paho-c-1.0.0,securemode=3,signmethod=hmacsha256,lan=C|"

#endif

其中,MQTT_CLINETID_KV ,即构造mqttClientId参数

由clientid+ MQTT_CLINETID_KV组成

修改MQTT_CLINETID_KV,

timestamp要和TIMESTAMP_VALUE对应

signmethod 固定写死hmacsha256,目前只实现了这个算法。


③ 修改源码使clientId实现自定义

        增加两行

char ClientID[1024] = {0};    

memcpy(ClientID, EXAMPLE_DEVICE_CLIENTID,strlen(EXAMPLE_DEVICE_CLIENTID));

       修改三行

memcpy(clientId, ClientID, strlen(ClientID));

memcpy(clientId + strlen(ClientID), MQTT_CLINETID_KV, strlen(MQTT_CLINETID_KV));

memset(clientId + strlen(ClientID) + strlen(MQTT_CLINETID_KV), 0, 1);    

     如图:


④ 构造mqttUsername

   (无需修改)

    固定DeviceName&ProductKey的格式


按字典升序组装content

   修改这一段代码为

#ifdef MQTTAUTH

memcpy(macSrc, "clientId", strlen("clientId"));

memcpy(macSrc + strlen(macSrc), ClientID, strlen(ClientID));

memcpy(macSrc + strlen(macSrc), "deviceName", strlen("deviceName"));

#else

memcpy(macSrc, "deviceName", strlen("deviceName"));

#endif

memcpy(macSrc + strlen(macSrc), deviceName, strlen(deviceName));

memcpy(macSrc + strlen(macSrc), "productKey", strlen("productKey"));

memcpy(macSrc + strlen(macSrc), productKey, strlen(productKey));

#ifdef MQTTAUTH

memcpy(macSrc + strlen(macSrc), "timestamp", strlen("timestamp"));

memcpy(macSrc + strlen(macSrc), TIMESTAMP_VALUE, strlen(TIMESTAMP_VALUE));

#else

memcpy(macSrc + strlen(macSrc), "random", strlen("random"));

memcpy(macSrc + strlen(macSrc), RANDOM_VALUE, strlen(RANDOM_VALUE));

#endif

clientIdqwertdeviceNameIoTDeviceDemo1productKeya16hDZJpRCltimestamp1655198877533

 添加一行:加个content的打印,调试用,content没有组装对也会导致签名错误。(易错点)

 printf("content:%s\n",macSrc);    

⑥ 使用设备密钥(DeviceSecret)对字典升序后的content进行签名,构造mqttPassword参数

 (无需修改代码)

   调用utils_hmac_sha256接口,进行hamcsha256运算,目前只实现了这个算法。


⑦ 编译&运行,验证结果

输入编译&运行指令:

     gcc aiot_mqtt_sign.c -o test1

    ./test1


另一边,用工具计算出的结果:


对比一下工具计算出来的结果和mqttPassword,两者是完全匹配的。(这一步还不能完全说明签名是正确的,只能说代码逻辑没有问题,但是如果content不对,最终的签名和阿里云还是不匹配的。)


然后使用这三个参数,mqttClientId,mqttUsername,mqttPaassword验证是否可以通过阿里云物联网平台认证:

mqttClientId: qwert|timestamp=1655198877533,_v=paho-c-1.0.0,securemode=3,signmethod=hmacsha256,lan=C|

mqttUsername: IoTDeviceDemo1&a16hDZJpRCl

mqttPassword: 24F9D22DA8DE6D8BE6****(马赛克)****E982BB357DF6FBA037D769F

MQTT.FX用这几个参数成功接入,如图

到这一步才能说明签名是完全正确的。



2-2-2 一型一密预注册

注意检查产品的动态注册开关是否已打开。


在一机一密的代码基础上进行修改

①  注释掉MQTTAUTH 、REGNWL宏,留下REGISTER


② 增加ProductSecret和Radom的宏定义,DeviceName换一个设备(未激活状态),以及mqttClientId


#ifdef REGISTER

#define EXAMPLE_DEVICE_NAME "IoTDeviceDynamicDemo1"

#define EXAMPLE_PRODUCT_SECRET "oa7oO91coNaHT6OS"

#define RANDOM_VALUE "123456789"

/*一型一密预注册,这里用老版公共实例测试,如果是企业实例或者新版公共实例,需要加一个instanceId参数*/ #define MQTT_CLINETID_KV "|securemode=2,authType=register,random=123456789,signmethod=hmacsha256|"

#endif

   其中,MQTT_CLINETID_KV ,即构造mqttClientId参数

    由clientid+ MQTT_CLINETID_KV组成

     修改MQTT_CLINETID_KV,

     timestamp要和TIMESTAMP_VALUE对应

     signmethod 固定写死hmacsha256,目前只实现了这个算法。


③ 构造mqttUsername

   (无需修改)

    固定DeviceName&ProductKey的格式


④ 按字典升序组装content

    在一机一密的代码基础上,无需再改,注意前边的宏定义REGISTER

    注意:一型一密认证的content不需要clientid和timestamp这两个参数,另外加了一个random参数


 content:deviceNameIoTDeviceDynamicDemo1productKeya16hDZJpRClrandom123456789


⑤ 使用产品密钥(ProductSecret)对字典升序后的content进行签名,构造mqttPassword参数

    (不需修改),注意前边的宏定义REGISTER

   调用utils_hmac_sha256接口,进行hamcsha256运算,目前只实现了这个算法。


⑥ 编译&运行,验证结果

输入编译&运行指令:

     gcc aiot_mqtt_sign.c -o test2

    ./test2


另一边,用工具计算出的结果:

对比一下工具计算出来的结果和mqttPassword,两者是完全匹配的。(这一步还不能完全说明签名是正确的,只能说代码逻辑没有问题,但是如果content不对,最终的签名和阿里云还是不匹配的。)


然后使用这三个参数,mqttClientId,mqttUsername,mqttPaassword验证是否可以通过阿里云物联网平台成功进行注册:

mqttClientId: qwert|securemode=2,authType=register,random=123456789,signmethod=hmacsha256|

mqttUsername: IoTDeviceDynamicDemo1&a16hDZJpRCl

mqttPassword: 102469CD77D18973AB6EB0E0****(马赛克)****BF9056767E529BBF5DEB4CE


MQTT.FX用这几个参数成功进行动态注册,如图

注意勾选SSL


到这一步才能说明签名是完全正确的。


2-2-3 一型一密免预注册

在一型一密预注册的代码基础上进行修改

注释掉MQTTAUTH 、REGISTER宏,留下REGNWL


②  增加ProductSecret和Radom的宏定义,以及mqttClientId,自定义一个DeviceName。

#ifdef REGNWL

/*一型一密免预注册,这里用老版公共实例测试,如果是企业实例或者新版公共实例,需要加一个instanceId参数*/ #define EXAMPLE_PRODUCT_SECRET "oa7oO**马赛克**aHT6OS"

#define EXAMPLE_DEVICE_NAME "IoTDeviceDynamicDemo2"

#define RANDOM_VALUE "123456789"

#define MQTT_CLINETID_KV "|securemode=-2,authType=regnwl,random=123456789,signmethod=hmacsha256|"

#endif

   其中,MQTT_CLINETID_KV ,即构造mqttClientId参数    

   由clientid+ MQTT_CLINETID_KV组成

     修改MQTT_CLINETID_KV,

     timestamp要和TIMESTAMP_VALUE对应

     signmethod 固定写死hmacsha256,目前只实现了这个算法。


③ 构造mqttUsername

   (无需修改)

    固定DeviceName&ProductKey的格式


④ 按字典升序组装content

    在一型一密预注册的代码基础上,(无需修改),注意前边的宏定义REGNWL

    注意:一型一密认证的content不需要clientid和timestamp这两个参数,另外加了一个random参数

 content:deviceNameIoTDeviceDynamicDemo2productKeya16hDZJpRClrandom123456789

⑤ 使用产品密钥(ProductSecret)对字典升序后的content进行签名,构造mqttPassword参数

    (不需修改),注意前边的宏定义REGISTER

   调用utils_hmac_sha256接口,进行hamcsha256运算,目前只实现了这个算法。


⑥ 编译&运行,验证结果

输入编译&运行指令:

     gcc aiot_mqtt_sign.c -o test3

    ./test3


另一边,用工具计算出的结果:


对比一下工具计算出来的结果和mqttPassword,两者是完全匹配的。(这一步还不能完全说明签名是正确的,只能说代码逻辑没有问题,但是如果content不对,最终的签名和阿里云还是不匹配的。)


然后使用这三个参数,mqttClientId,mqttUsername,mqttPaassword验证是否可以通过阿里云物联网平台成功进行免预注册:

mqttClientId: qwert|securemode=-2,authType=regnwl,random=123456789,signmethod=hmacsha256|

mqttUsername: IoTDeviceDynamicDemo2&a16hDZJpRCl

mqttPassword: 59ABF3B1E0C6FF841705393****马赛克****6F0CD1E6F578193D126ABB42


MQTT.FX用这几个参数成功进行免预注册,如图

注意勾选SSL


而且控制台上可以看到通过免预注册的成功添加的设备


到这一步才能说明签名是完全正确的。



相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
19天前
|
消息中间件 网络协议 物联网
MQTT常见问题之物联网设备端申请动态注册时MQTT服务不可用如何解决
MQTT(Message Queuing Telemetry Transport)是一个轻量级的、基于发布/订阅模式的消息协议,广泛用于物联网(IoT)中设备间的通信。以下是MQTT使用过程中可能遇到的一些常见问题及其答案的汇总:
|
19天前
|
物联网 网络性能优化 API
MQTT常见问题之MQTT获取某个时间点的设备的状态失败如何解决
MQTT(Message Queuing Telemetry Transport)是一个轻量级的、基于发布/订阅模式的消息协议,广泛用于物联网(IoT)中设备间的通信。以下是MQTT使用过程中可能遇到的一些常见问题及其答案的汇总:
|
19天前
|
物联网 Serverless
MQTT常见问题之通过mqtt控制台查询不到设备轨迹如何解决
MQTT(Message Queuing Telemetry Transport)是一个轻量级的、基于发布/订阅模式的消息协议,广泛用于物联网(IoT)中设备间的通信。以下是MQTT使用过程中可能遇到的一些常见问题及其答案的汇总:
|
19天前
|
监控 网络性能优化 网络安全
【MODBUS】Modbus主站为边缘设备通过MQTT协议上云
【MODBUS】Modbus主站为边缘设备通过MQTT协议上云
44 1
|
7月前
|
传感器 JavaScript 物联网
MQTT 保持活动计时器:让您的设备保持连接
MQTT 保持活动计时器:让您的设备保持连接
126 0
|
Java 测试技术 Shell
使用JMeter模拟设备通过MQTT发送数据
使用JMeter模拟设备通过MQTT发送数据
376 0
使用JMeter模拟设备通过MQTT发送数据
EMQ
|
传感器 算法 网络协议
MQTT 客户端自动重连最佳实践|构建可靠 IoT 设备连接
本文介绍在MQTT客户端代码实现过程中,自动重连逻辑设计的重要性与示例,帮助读者设计更为合理的重连代码,构建更加稳定可靠的物联网设备连接。
EMQ
924 0
MQTT 客户端自动重连最佳实践|构建可靠 IoT 设备连接
|
算法 物联网
设备通过mqtt通道的动态免预注册
一型一密认证方式下,同一产品下所有设备可以烧录相同的设备标志信息,即所有设备包含相同的产品证书(ProductKey和ProductSecret)。设备发送激活请求时,物联网平台会进行身份确认,认证通过后,下发设备接入所需信息。
354 0
|
19天前
|
消息中间件 网络协议 JavaScript
MQTT常见问题之微消息队列mqtt支持ipv6失败如何解决
MQTT(Message Queuing Telemetry Transport)是一个轻量级的、基于发布/订阅模式的消息协议,广泛用于物联网(IoT)中设备间的通信。以下是MQTT使用过程中可能遇到的一些常见问题及其答案的汇总:
|
19天前
|
消息中间件 物联网 Java
MQTT常见问题之微消息队列配置失败如何解决
MQTT(Message Queuing Telemetry Transport)是一个轻量级的、基于发布/订阅模式的消息协议,广泛用于物联网(IoT)中设备间的通信。以下是MQTT使用过程中可能遇到的一些常见问题及其答案的汇总:

热门文章

最新文章