天天看點

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
           
完美收官

繼續閱讀