由於以前從事過電信信令類工做,接觸較多的則是ASN.1中的BER、PER編碼,其中BER是基於TLV方式進行編碼,本文主要介紹一下TLV在自定義協議中的應用。java
經過該文章,你能夠肉眼看懂一些相似二進制通訊協議,並能夠嘗試封裝本身的通訊協議c++
協議可使雙方不須要了解對方的實現細節的狀況下進行通訊,所以雙方能夠是異構的,server能夠是c++,client能夠是java,基於相同的協議,咱們能夠用本身熟識的語言工具來實現。數組
協議通常由一個或多個消息組成,簡單的來講,消息就像是一個Table,由表頭(消息的字段定義,包括名稱與數據類型)與行(字段值)組成。網絡
約定好雙方交換數據的編解碼方式,包括一致的基本數據類型,業務類型,字節序、消息內容等。工具
能夠跟據業務須要進行定製,如對編解碼速度、網絡帶寬、用戶量等進行考量編碼
報頭(4字節描述數據體長度)+數據(字符串+分隔符或直接使用JSON),該方式實現簡單,在編解碼階段成本低、但在數據類型轉時成本較高,同時可能會較佔用帶寬。code
將協議以特定格式編碼爲字節數組,該種方式相較字符串編碼方式實現要求要高一些,但帶寬佔用相對小一些,本文主要介紹其中一種較經常使用的編碼方式TLV,即Tag\Length\Value。orm
TLV:TLV是指由數據的類型Tag,數據的長度Length,數據的值Value組成的結構體,幾乎能夠描任意數據類型,TLV的Value也能夠是一個TLV結構,正由於這種嵌套的特性,可讓咱們用來包裝協議的實現。server
如下將分別針對Tag、Length、Value進行解說:blog
Tag由一個或多個字節組成,上圖描述首字節0~7位的具體含義
後續字節採用每一個字節的0~6位(即7bit)來存儲Tag Value, 第7位用來標識是否還有後續字節。
如下提供Tag編碼的JAVA實現
/** * 生成 Tag ByteArray * * @param tagValue Tag 值,即協議中定義的交易類型 或 基本數據類型 * @param frameType TLV類型,Tag首字節最左兩bit爲00:基本類型,01:私有類型(自定義類型) * @param dataType 數據類型,Tag首字節第5位爲0:基本數據類型,1:結構類型(TLV類型,即TLV的V爲一個TLV結構) * @return Tag ByteArray */ 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); }
描述Value部分所佔字節的個數,編碼格式分兩類:定長方式(DefiniteForm)和不定長方式(IndefiniteForm),其中定長方式又包括短形式與長形式。
定長方式中,按長度是否超過一個八位,又分爲短、長兩種形式,編碼方式以下:
如下提供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; } }
Length所在八位組固定編碼爲0x80,但在Value編碼結束後以兩個0x00結尾。這種方式使得能夠在編碼沒有徹底結束的狀況下,能夠先發送部分數據給對方。
由一個或多個值組成 ,值能夠是一個原始數據類型(Primitive Data),也能夠是一個TLV結構(Constructed Data)
若是各位看官充分消化了第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 |
這時須要對基本數據類型(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 | 0 |
上表須要關注的是數據類型對應的Tag值與Length值
名稱 | 消息 | 標記:Tag |
---|---|---|
設備故障碼 | DEVICE_FAULT_1 | 1 |
經過三層TLV嵌套,完成協議消息的封包
Tips:每層嵌套都有2個或以上的字節增長(Tag和Length),通常通訊雙方能夠按照協議對數據類型進行推定,因此你們能夠根據實際須要,決定是否省略第三層的Tag和Length,便可經過配置文件或其它方式讓程序瞭解字段的類型,從而下降數據包的大小,節省流量。
從上面能夠看出,TLV是一種與業務無關的編碼方式,能夠較容易用來實現自定義協議