MQTT协议之剩余长度编解码算法实现

10 篇文章 2 订阅

MQTT协议中,剩余长度使用的是一种变长度的编码方案,其剩余长度 = 可变报头长度 + 负载长度。
所以剩余长度最少占用1个字节,最多占用4个字节。下图就是MQTT协议中总结的剩余长度不同的取值范围,对应所占用的字节数。
**加粗样式**

而这些范围对应到具体数据的编解码由下面的算法来实现。

下图是从MQTT协议中copy的其编码解码算法

在这里插入图片描述

MQTT采用大端序列传输数据,想知道自己计算机是大端还是小端可以通过一个int64的数据1,左移大于32位,强转为byte,看看是0还是大于0的,是0就是大端的

一个字节包含8个二进制位,分别是Bit0 ~ Bit7,在MQTT协议中都是无符号的。正常情况下,一个字节所表示的范围是0 ~ 255,但是在MQTT协议剩余长度中将一个字节分成了两个部分。
在这里插入图片描述

剩余长度中字节分为两个部分使用,如上图两个红框框

Bit7当做一个进位标志,Bit0 ~ Bit6表示数值,所以一个字节表达的数值范围变成了0 ~ 127,接下来我们举几个例子,看看剩余长度是如何编码的。剩余长度是逢128的整数倍进位,进位就要多加一个字节。如果Bit7是1表示前面有进位,如果是0就表示没有进位。下面看例子。

(1)100,100/128=0,等于0无需进位,所以Bit7是0,然后100%128=100,所以Bit0~Bit6就是0x64,剩余长度就是1个字节。

(2)1000,1000/128=7,不等于0需要进位,然后7/128=0,等于0无需再进位,所以总体进1位,需要2个字节,第1个字节Bit7是1,第2个字节Bit7是0。然后1000%128=104,所以第1个字节Bit0 ~ Bit6就是0x68,然后7%128=7,所以第2个字节Bit0~Bit6就是0x07,然后再结合上Bit7的话,就是 0xE8 和0x07

(3)100000,100000/128=781,不等于0需要进位,然后781/128=6,不等于0再进一位,6/128=0,等于0无需再进位了,所以总体进2位,需要3个字节。第1个字节Bit7是1,第2个字节Bit7是1,第3个字节Bit7是0。然后100000%128=32,所以第1个字节Bit0 ~ Bit6就是0x20,然后781%128=13,所以第2个字节Bit0 ~ Bit6就是0x0D,然后6%128=6,所以第3个字节Bit0~Bit6就是0x06,然后再结合上Bit7的话,就是 0xA0 0x8D 0x06。

(4)100000000,100000000/128=781250,不等于0需要进位,781250/128= 6103,不等于0需要再进位,6103/128=47,不等于0需要再进位,47/128=0,等于0无需再进位,所以总体进3位,需要4个字节,第1个字节Bit7是1,第2个字节Bit7是1,第3个字节Bit7是1,第4个字节Bit7是0。然后100000000%128=0, 所以第1个字节Bit0 ~ Bit6就是0x00,然后781250%128=66,所以第2个字节Bit0 ~ Bit6就是0x42,然后6103%128=87,所以第3个字节Bit0 ~ Bit6就是0x57,然后47%128=47,所以第4个字节Bit0~Bit6就是0x2F,再结合上Bit7的话,就是0x80 0xC2 0xD7 0x2F

而具体对应代码实现还需要稍稍处理一下

func Encode(x int32) []byte {
	encodedByte := x % 128
	b := make([]byte,4)
	var i = 0
	x = x / 128
	if x > 0{
		encodedByte = encodedByte | 128
		b[i] = byte(encodedByte)
		i++
	}
	for x > 0{
		encodedByte = x % 128
		x = x / 128
		if x > 0{
			encodedByte = encodedByte | 128
			b[i] = byte(encodedByte)
			i++
		}
	}
	b[i] = byte(encodedByte)
	return b[:i+1]
}

来测试一下

func TestEncode(t *testing.T) {
	args := []int32{100,1000,100000,100000000}
	for _, arg := range args {
		fmt.Println("编码数据:"+strconv.Itoa(int(arg)))
		b := Encode(arg)
		fmt.Printf("编码后字节:%b\n",b)
		fmt.Printf("编码后16进制展示:%x\n",b)
		fmt.Printf("编码后各位数据:%d\n",b)
		data := make([]byte,4)
		j := 0
		for i := 4-len(b); i < 4; i++{
			data[i] = b[j]
			j++
		}
		fmt.Println("编码后数据:"+strconv.Itoa(int(binary.BigEndian.Uint32(data))))
		fmt.Println("=====================")
	}
}

执行结果如下:

=== RUN   TestEncode
编码数据:100
编码后字节:[1100100]
编码后16进制展示:64
编码后各位数据:[100]
编码后数据:100
=====================
编码数据:1000
编码后字节:[11101000 111]
编码后16进制展示:e807
编码后各位数据:[232 7]
编码后数据:59399
=====================
编码数据:100000
编码后字节:[10100000 10001101 110]
编码后16进制展示:a08d06
编码后各位数据:[160 141 6]
编码后数据:10521862
=====================
编码数据:100000000
编码后字节:[10000000 11000010 11010111 101111]
编码后16进制展示:80c2d72f
编码后各位数据:[128 194 215 47]
编码后数据:2160252719
=====================
--- PASS: TestEncode (0.00s)
PASS

编码工作完成,可以搞发送数据了,但是接收方怎么解码呢?
在这里插入图片描述

我们就用上面第(3)例子编码结果: 0xA0 0x8D 0x06来演示,我们分两步走

(1)判断有多少个字节,剩余长度最少1个字节最多4个字节,所以我们先看0xA0,Bit7是1,那么说明有进位,所以目前的情况,总共有2个字节,再看0x8D,Bit7又是1,那么现在的话,是3个字节了,再看0x06,Bit7是0,说明没有进位了,剩余长度到此为止,总共3个字节。

(2)计算剩余长度,注意不要把第1个和第2个字节的Bit7算进去,0x06128128+0x0D *128+0x20=100000

解码算法代码实现:

func Decode(b []byte) int32 {
	if len(b)==0{
		return 0
	}
	var(
		value, mu int32 = 0,1
		ec byte
		i = 0
	)
	ec ,i = b[i], i+1
	value += int32(ec & 127) * mu
	mu *= 128
	for (ec & 128) != 0{
		ec ,i = b[i], i+1
		value += int32(ec & 127) * mu
		if mu > 128*128*128{
			panic("Malformed Variable Byte Integer")
		}
		mu *= 128
	}
	return value
}

这次以100000的例测试

func TestEncode(t *testing.T) {
	args := []int32{100000}
	for _, arg := range args {
		fmt.Println("编码数据:"+strconv.Itoa(int(arg)))
		b := Encode(arg)
		fmt.Printf("编码后字节:%b\n",b)
		fmt.Printf("编码后16进制展示:%x\n",b)
		fmt.Printf("编码后各位数据:%d\n",b)
		data := make([]byte,4)
		j := 0
		for i := 4-len(b); i < 4; i++{
			data[i] = b[j]
			j++
		}
		fmt.Println("编码后数据:"+strconv.Itoa(int(binary.BigEndian.Uint32(data))))
		fmt.Println("解码后数据:"+strconv.Itoa(int(Decode(b))))
		fmt.Println("=====================")
	}
}

执行结果如下

=== RUN   TestEncode
编码数据:100000
编码后字节:[10100000 10001101 110]
编码后16进制展示:a08d06
编码后各位数据:[160 141 6]
编码后数据:10521862
解码后数据:100000
=====================
--- PASS: TestEncode (0.00s)
PASS

完美收官

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
设备端与阿里云IoT平台建立MQTT通道的CONNECT报文有三个关键参数:客户端标识符 (ClientId) ,用户名(User Name),密码(Password)。这三个参数由设备三元组(productKey,deviceName,deviceSecret)按照阿里云IoT签名规则生成。 参数 生成方式 描述 username deviceName+"&"+productKey password sign_hmac(deviceSecret,content) sign_hmac为mqttClientId中的signmethod content为 "clientId${id}deviceName${deviceName}productKey${productKey}timestamp${timestamp}" 其中 id:表示客户端ID,64字符内。 timestamp:表示当前时间毫秒值。 clientId id+"|securemode=3,signmethod=hmacsha1,timestamp="+timestamp+"|" id:表示客户端ID,64字符内。 timestamp:表示当前时间毫秒值。 securemode:表示安全模式:可选值有2(TLS加密)和3(非加密) signmethod:表示签名算法类型。支持hmacmd5,hmacsha1和hmacsha256 2. 三元组接入示例 2.1 设备三元组信息 设备三元组 productKey = a14Xib5kdYd deviceName = light1983432 deviceSecret = oLyaKqVxtRvjH284LdhqVgVUx1UPy6zq 建立MQTT连接时参数 clientId = SN1928339 timestamp = 1539421321846 signmethod = hmacsha1 2.2 参数生成结果 mqttClientId = SN1928339|securemode=3,signmethod=hmacsha1,timestamp=1539421321846| mqttUsername = light1983432&a14Xib5kdYd mqttPassword = b2488041f64f425016b467ee1c94959ebd592ad1 生成password的content content=clientIdSN1928339deviceNamelight1983432productKeya14Xib5kdYdtimestamp1539421321846 2.3 建立连接 mqttClientId作为MQTT客户端标识符 (ClientId) mqttUsername作为MQTT用户名(User Name) mqttPassword作为MQTT密码(Password) 在线Password生成算法验证

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值