天天看點

java tlv協定_看懂通信協定:自定義通信協定設計之TLV編碼應用

因為之前從事過電信信令類工作,接觸較多的則是ASN.1中的BER、PER編碼,其中BER是基于TLV方式進行編碼,本文主要介紹一下TLV在自定義協定中的應用。

通過該文章,你可以肉眼看懂一些類似二進制通信協定,并可以嘗試封裝自己的通信協定

1. 通信協定

協定可以使雙方不需要了解對方的實作細節的情況下進行通信,是以雙方可以是異構的,server可以是c++,client可以是java,基于相同的協定,我們可以用自己熟識的語言工具來實作。

協定一般由一個或多個消息組成,簡單的來說,消息就像是一個Table,由表頭(消息的字段定義,包括名稱與資料類型)與行(字段值)組成。

2. 自定義通信協定

約定好雙方交換資料的編解碼方式,包括一緻的基本資料類型,業務類型,位元組序、消息内容等。

3. 編碼方式

可以跟據業務需要進行定制,如對編解碼速度、網絡帶寬、使用者量等進行考量

3.1. 基于字元串編碼

報頭(4位元組描述資料體長度)+資料(字元串+分隔符或直接使用JSON),該方式實作簡單,在編解碼階段成本低、但在資料類型轉時成本較高,同時可能會較占用帶寬。

3.2. 基于二進制編碼

将協定以特定格式編碼為位元組數組,該種方式相較字元串編碼方式實作要求要高一些,但帶寬占用相對小一些,本文主要介紹其中一種較常用的編碼方式TLV,即Tag\Length\Value。

4. TLV編碼介紹( 其中一種實作介紹 )

TLV:TLV是指由資料的類型Tag,資料的長度Length,資料的值Value組成的結構體,幾乎可以描任意資料類型,TLV的Value也可以是一個TLV結構,正因為這種嵌套的特性,可以讓我們用來包裝協定的實作。

java tlv協定_看懂通信協定:自定義通信協定設計之TLV編碼應用

以下将分别針對Tag、Length、Value進行解說:

4.1. Tag 描述Value的資料類型,TLV嵌套時可以用于描述消息的類型

java tlv協定_看懂通信協定:自定義通信協定設計之TLV編碼應用

Tag由一個或多個位元組組成,上圖描述首位元組0~7位的具體含義

1) Tag首節字說明

第6~7位:表示TLV的類型,00表示TLV描述的是基本資料類型(Primitive Frame, int,string,long...),01表示使用者自定義類型(Private Frame,常用于描述協定中的消息)。

第5位:表示Value的編碼方式,分别支援Primitive及Constructed兩種編碼方式, Primitive指以原始資料類型進行編碼,Constructed指以TLV方式進行編碼,0表示以Primitive方式編碼,1表示以Constructed方式編碼。

第0~4位:當Tag Value小于0x1F(31)時,首位元組0~4位用來描述Tag Value,否則0~4位全部置1,作為存在後續位元組的标志,Tag Value将采用後續位元組進行描述。

java tlv協定_看懂通信協定:自定義通信協定設計之TLV編碼應用

2) Tag後續位元組說明

後續位元組采用每個位元組的0~6位(即7bit)來存儲Tag Value, 第7位用來辨別是否還有後續位元組。

第7位:描述是否還有後續位元組,1表示有後續位元組,0表示沒有後續位元組,即結束位元組。

第0~6位:填充Tag Value的對應bit(從低位到高位開始填充),如:Tag Value為:0000001 11111111 11111111 (10進制:131071), 填充後實際位元組内容為:10000111 11111111 01111111。

java tlv協定_看懂通信協定:自定義通信協定設計之TLV編碼應用

以下提供Tag編碼的JAVA實作

public byte[] parseTag(int tagValue, int frameType, int dataType) {

int size = 1;

rawTag = frameType | dataType | tagValue;

if (tagValue < 0x1F) {

// 1 byte tag

rawTag = frameType | dataType | tagValue;

} else {

// mutli byte tag

rawTag = frameType | dataType | 0x1F;

if (tagValue < 0x80) {

rawTag <<= 8;

rawTag |= tagValue & 0x7F;

} else if (tagValue < 0x3FFF) {

rawTag <<= 16;

rawTag |= (((tagValue & 0x3FFF) >> 7 & 0x7F) | 0x80) << 8;

rawTag |= ((tagValue & 0x3FFF) & 0x7F);

} else if (tagValue < 0x3FFFF) {

rawTag <<= 24;

rawTag |= (((tagValue & 0x3FFFF) >> 14 & 0x7F) | 0x80) << 16;

rawTag |= (((tagValue & 0x3FFFF) >> 7 & 0x7F) | 0x80) << 8;

rawTag |= ((tagValue & 0x3FFFF) & 0x7F);

}

}

return intToByteArray(rawTag);

}

4.2. Length 描述Value的長度

描述Value部分所占位元組的個數,編碼格式分兩類:定長方式(DefiniteForm)和不定長方式(IndefiniteForm),其中定長方式又包括短形式與長形式。

1) 定長方式

定長方式中,按長度是否超過一個八位,又分為短、長兩種形式,編碼方式如下:

短形式: 位元組第7位為0,表示Length使用1個位元組即可滿足Value類型長度的描述,範圍在0~127之間的。

java tlv協定_看懂通信協定:自定義通信協定設計之TLV編碼應用

長形式:

即Value類型的長度大于127時,Length需要多個位元組來描述,這時第一個位元組的第7位置為1,0~6位用來描述Length值占用的位元組數,然後直将Length值轉為byte後附在其後,如: Value大小占234個位元組(11101010),由于大于127,這時Length需要使用兩個位元組來描述,10000001 11101010

java tlv協定_看懂通信協定:自定義通信協定設計之TLV編碼應用

以下提供Length定長方式的JAVA實作

public byte[] parseLength(int length) {

if (length < 0) {

throw new IllegalArgumentException();

} else

// 短形式

if (length < 128) {

byte[] actual = new byte[1];

actual[0] = (byte) length;

return actual;

} else

// 長形式

if (length < 256) {

byte[] actual = new byte[2];

actual[0] = (byte) 0x81;

actual[1] = (byte) length;

return actual;

} else if (length < 65536) {

byte[] actual = new byte[3];

actual[0] = (byte) 0x82;

actual[1] = (byte) (length >> 8);

actual[2] = (byte) length;

return actual;

} else if (length < 16777126) {

byte[] actual = new byte[4];

actual[0] = (byte) 0x83;

actual[1] = (byte) (length >> 16);

actual[2] = (byte) (length >> 8);

actual[3] = (byte) length;

return actual;

} else {

byte[] actual = new byte[5];

actual[0] = (byte) 0x84;

actual[1] = (byte) (length >> 24);

actual[2] = (byte) (length >> 16);

actual[3] = (byte) (length >> 8);

actual[4] = (byte) length;

return actual;

}

}

2) 不定長方式

Length所在八位組固定編碼為0x80,但在Value編碼結束後以兩個0x00結尾。這種方式使得可以在編碼沒有完全結束的情況下,可以先發送部分資料給對方。

java tlv協定_看懂通信協定:自定義通信協定設計之TLV編碼應用

4.3. Value 描述資料的值

由一個或多個值組成 ,值可以是一個原始資料類型(Primitive Data),也可以是一個TLV結構(Constructed Data)

1) Primitive Data 編碼

java tlv協定_看懂通信協定:自定義通信協定設計之TLV編碼應用

2) Constructed Data 編碼

java tlv協定_看懂通信協定:自定義通信協定設計之TLV編碼應用

5. TLV編碼應用

如果各位看官充分消化了第4點TLV的描述,自然可以很容易将其應用到自定義協定之中,其實我們隻要定制各種TLV自定義類型(Private Frame)與協定中的消息一一對應更行了

下面将以一個簡單的協定來描述TLV的應用,假設該協定消息定義如下:

消息名稱

裝置故障碼(DEVICE_FAULT_1)

Tag值

1

公共字段定義

名稱

字段

Tag值

長度

類型

裝置編号

DeviceNo

1

4

Integer

裝置版本号

DeviceVersion

2

12

String

請求定義

名稱

字段

Tag值

長度

類型

錯誤碼

FaultCode

3

4

Integer

響應定義

名稱

字段

Tag值

長度

類型

響應碼

ResponseCode

3

4

Integer

響應資訊

ResponseMsg

4

-1

String

5.1 基本資料類型約定

這時需要對基本資料類型(Primitive Data)進行約定,以便通信雙方以一緻的方式進行資料轉換,這也作為協定制定的一部分

基本資料類型約定

名稱

類型

标記:Tag

長度:Length

值範圍:Value

布爾

Boolean

10進制:1, 2進制:00000001

1

1:true .. 0:false

小整型

Tiny

10進制:2, 2進制:00000010

1

-127 .. 127

無符号小整型

UTiny

10進制:3, 2進制:00000011

1

0 .. 255

短整型

Short

10進制:4, 2進制:00000100

2

-32768 .. 32767

無符号短整型

UShort

10進制:5, 2進制:00000101

2

0 .. 65535

整型

Integer

10進制:6, 2進制:00000110

4

-2147483648 .. 2147483648

無符号整型

UInteger

10進制:7, 2進制:00000111

4

0 .. 4294967295

長整型

Long

10進制:8, 2進制:00001000

8

-2^64 .. 2^64

無符号長整型

ULong

10進制:9, 2進制:00001001

8

0 .. 2^128-1

單精浮點類型

Float

10進制:10, 2進制:00001010

4

-2^128 .. 2^128

雙精浮點類型

Double

10進制:11, 2進制:00001011

8

-2^1024 .. 2^1024

字元類型

Char

10進制:12, 2進制:00001100

1

ASCII

字元串類型

String

10進制:13, 2進制:00001101

可變

由一個或多個Char組成

組合類型

Complex

10進制:14, 2進制:00001110

可變

由一個或多個基本類型1~9組成,由協定兩端雙方進行約定編解碼

空類型

Null

10進制:15, 2進制:00001111

上表需要關注的是資料類型對應的Tag值與Length值

5.2 協定消息約定

名稱

消息

标記:Tag

裝置故障碼

DEVICE_FAULT_1

1

5.3 示例

通過三層TLV嵌套,完成協定消息的封包

第一層:與協義消息對應

第二層:與消息字段對應

第三層:與字段值對應,包括其值的類型資訊

java tlv協定_看懂通信協定:自定義通信協定設計之TLV編碼應用

Tips:每層嵌套都有2個或以上的位元組增加(Tag和Length),一般通信雙方可以按照協定對資料類型進行推定,是以大家可以根據實際需要,決定是否省略第三層的Tag和Length,即可通過配置檔案或其它方式讓程式了解字段的類型,進而降低資料包的大小,節省流量。

6 總結

從上面可以看出,TLV是一種與業務無關的編碼方式,可以較容易用來實作自定義協定