天天看点

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

MQTT协议中,剩余长度使用的是一种变长度的编码方案,其剩余长度 = 可变报头长度 + 负载长度。

所以剩余长度最少占用1个字节,最多占用4个字节。下图就是MQTT协议中总结的剩余长度不同的取值范围,对应所占用的字节数。

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

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

下图是从MQTT协议中copy的其编码解码算法
MQTT协议之剩余长度编解码算法实现
MQTT采用大端序列传输数据,想知道自己计算机是大端还是小端可以通过一个int64的数据1,左移大于32位,强转为byte,看看是0还是大于0的,是0就是大端的

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

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

           

编码工作完成,可以搞发送数据了,但是接收方怎么解码呢?

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

我们就用上面第(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
           
完美收官

继续阅读