轉(zhuǎn)載自https://segmentfault.com/a/1190000022356844
二進(jìn)制編碼傳輸協(xié)議
思考
- 何為二進(jìn)制協(xié)議傳輸,何為文本協(xié)議數(shù)據(jù)傳輸真朗?
- 網(wǎng)絡(luò)編程中數(shù)據(jù)協(xié)議的制定方式有哪些此疹?
-
Protobuf
等二進(jìn)制數(shù)據(jù)序列化傳輸協(xié)議的機(jī)制是什么?
在網(wǎng)絡(luò)編程中遮婶,經(jīng)郴人椋看到要求數(shù)據(jù)要以二進(jìn)制的方式進(jìn)行傳輸,起初我很不理解旗扑,為什么要刻意的說明二進(jìn)制方式呢蹦骑?數(shù)據(jù)在底層的傳輸不都是二進(jìn)制流嗎?而且還引出了 pack/unpack
方法簇臀防。
我們經(jīng)常用到的 rpc
眠菇,比如 json-rpc
是以 文本方式
傳輸序列化的數(shù)據(jù)的。grpc(protobuf)
, thrift
都是以 二進(jìn)制方式
傳輸數(shù)據(jù)的袱衷。所以到底何為二進(jìn)制傳輸呢捎废?
大家可以先想一下日常中發(fā)送請求時(shí)經(jīng)常用到的方式: xml
, json
, formData
,他們雖然格式不同致燥,但都有一個(gè)特征登疗,自帶描述信息(直白說就是攜帶 參數(shù)名
),像 文本
一樣嫌蚤,能很直觀的看到數(shù)據(jù)表征的內(nèi)容辐益。
如果我們事先定義了數(shù)據(jù)中的n~m
個(gè)字節(jié)位固定作為某參數(shù)的數(shù)據(jù)段,就可以免去 參數(shù)名
所帶來的額外開銷脱吱。比如 0 ~ 10
字節(jié)為 account
荷腊,11 ~ 24
字節(jié)為 passowrd
。又因?yàn)橛脩裘蛎艽a是非定長的急凰,而解析數(shù)據(jù)時(shí)又要根據(jù)字節(jié)位精準(zhǔn)的截取女仰,所以我們需要對數(shù)據(jù)項(xiàng)進(jìn)行打包填充猜年,使其固定字節(jié)長度,而后在服務(wù)端進(jìn)行解包疾忍,pack/unpack
便可以實(shí)現(xiàn)此功能乔外。
白話
tcp
協(xié)議是日常中最為常見的二進(jìn)制協(xié)議,協(xié)議體的字節(jié)位都有約定好的表征一罩。
http
在廣義上來說也是二進(jìn)制模式杨幼,使用 \r\n
對協(xié)議進(jìn)行解包,解析聂渊,但 http
攜帶的數(shù)據(jù)通常都是文本模式的差购,比如 "sqrtcat" 占了 7 個(gè)字節(jié),在文本 or 二進(jìn)制模式下沒什么區(qū)別,但"29",以文本模式發(fā)送需要 2bytes紫岩,以二進(jìn)制模式打包至字符類型抬伺,只需要 1bytes肋杖。
二進(jìn)制為何能提高數(shù)據(jù)傳輸效率:
- 根據(jù)協(xié)議約定,省去參數(shù)名所占用的字節(jié),縮減了數(shù)據(jù)。
- 將數(shù)值類型的數(shù)據(jù)打包至相應(yīng)范圍內(nèi)的二進(jìn)制彰居,節(jié)省了空間,4bytes能表示 32 位的文本數(shù)值撰筷,但文本數(shù)據(jù)值要 32bytes陈惰。
- 在一定程度上可以起到加密數(shù)據(jù)的作用,如果第三方不知道數(shù)據(jù)協(xié)議毕籽,就沒有辦法截取相應(yīng)的字節(jié)為獲取數(shù)據(jù)奴潘,或得到數(shù)據(jù)的表征。
文本方式傳輸
日常開發(fā)影钉,比如發(fā)送一個(gè)用戶注冊 http協(xié)議
請求,發(fā)送的數(shù)據(jù)格式分別如下:
<pre class="php hljs language-php" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">$registerData = [
"account" => "sqrtcat",
"password" => "123456"
];</pre>
formData 31bytes
<pre class="bash hljs language-bash" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">account=sqrtcat&password=123456</pre>
json 41bytes
<pre class="json hljs language-json" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">{"account":"sqrtcat","password":"123456"}</pre>
xml 94bytes
<pre class="xml hljs language-xml" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><?xml version="1.0" encoding="UTF-8" ?>
<account>sqrtcat</account>
<password>123456</password></pre>
以上三種皆為掘剪,我們可以很直觀的在數(shù)據(jù)體重得到各項(xiàng)參數(shù)平委。
二進(jìn)制傳輸方式
二進(jìn)制傳輸,離不開協(xié)議的制定夺谁。文本方式傳輸?shù)臄?shù)據(jù)可以自我描述廉赔,而二進(jìn)制方式傳輸?shù)臄?shù)據(jù),需要通過協(xié)議進(jìn)行解析和讀取匾鸥。
最簡單的蜡塌,參數(shù)定長的方式,account
固定為 11 位勿负,password
固定為 14 位馏艾,使用 pack
將數(shù)據(jù)填充至相應(yīng)的協(xié)議長度,發(fā)送,服務(wù)端按協(xié)議進(jìn)行字節(jié)長度的截取獲得對應(yīng)的參數(shù)值琅摩。
<pre class="php hljs language-php" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><?php
// binary protocal:
// |-- 11 bytes account --|-- 14 bytes password --|
password = "123456";
// pack
// A 以空白符對數(shù)據(jù)進(jìn)行填充 php 解包時(shí)會(huì)自動(dòng) trim 掉
// a 以 0x00 字符對數(shù)據(jù)進(jìn)行填充 php 解包時(shí)會(huì)保留 0x00
account, $password);
// send
echo "data pack to bin len: " . strlen(dataBin . PHP_EOL;
// unpack
dataBin);
var_dump($dataArr);
// result
data pack to bin len: 25
data pack to bin: sqrtcat 123456
array(2) {
["account"]=>
string(7) "sqrtcat"
["password"]=>
string(6) "123456"
}</pre>
對比文本方式發(fā)送铁孵,我們在協(xié)議和二進(jìn)制傳輸?shù)姆绞较拢挥昧?25bytes房资。這就滿足了蜕劝?并不能夠~,這種簡單協(xié)議的二進(jìn)制傳輸方式只是在一定場景下發(fā)揮了傳輸效率轰异,在某些場景下可能還不如文本方式岖沛。因?yàn)閲?yán)格的數(shù)據(jù)定長填充,可能會(huì)造成數(shù)據(jù)的冗余搭独,比如 account
只有一個(gè)字符 s
婴削,password
也只有一個(gè)字符 1
,在此協(xié)議下還是固定 25bytes戳稽,文本傳輸反而效率會(huì)高一些馆蠕。
二進(jìn)制傳輸敗北了?No惊奇,是我們協(xié)議太簡單互躬,不夠靈活,沒有最大程度上發(fā)揮協(xié)議+二進(jìn)制的高效性颂郎,可以說吼渡,協(xié)議下的二進(jìn)制傳輸方式,能做到絕對的高效于文本傳輸乓序,這里我們可以簡單的分析和模擬以二進(jìn)制方式傳輸?shù)?protobuf
的協(xié)議模式寺酪。
Protobuf 的二進(jìn)制傳輸
我們可以簡單分析下 protobuf
傳輸數(shù)據(jù)的方式:
- 定義
IDL
,其實(shí)就相當(dāng)于制定了協(xié)議體 - 生成
proto
文件替劈,得到具體的消息字段的參數(shù)項(xiàng)位
和參數(shù)長度位
映射的消息協(xié)議包寄雀。 - 發(fā)送端根據(jù)消息協(xié)議定義的參數(shù)數(shù)據(jù)類型(主要是變長 or 定長),將數(shù)據(jù)打包至相應(yīng)的二進(jìn)制格式陨献。
- 發(fā)送數(shù)據(jù)盒犹。
- 接收端按消息協(xié)議格式對二進(jìn)制數(shù)據(jù)進(jìn)行解析,獲得文本數(shù)據(jù)眨业。
這里原諒我自己造了兩個(gè)詞急膀,參數(shù)項(xiàng)位
和 參數(shù)長度位
,如何理解呢龄捡?通過下面模仿 protobuf
的協(xié)議示例來理解卓嫂。
定義消息體的IDL
<pre class="protobuf hljs language-protobuf" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">message RegisterRequest {
string account = 1; // 數(shù)據(jù)位1 type string name account
string password = 2; // 數(shù)據(jù)位2 type string name password
tinyint age = 3; // 數(shù)據(jù)位3 type tinyint name age
}</pre>
注意下面是我自己仿 protobuf 寫的一套 php 二進(jìn)制序列化組件,完整版已放置 github 支持的數(shù)據(jù)類型還是很全面的
:protoBin聘殖。
協(xié)議數(shù)據(jù)類型的二進(jìn)制格式約定
主要是定義哪些類型是定長晨雳,哪些類型是變長行瑞,變長類型還需給定長度位的字節(jié)數(shù)。
<pre class="php hljs language-php" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><?php
/**
協(xié)議數(shù)據(jù)類型
//| 參數(shù)位1(變長數(shù)據(jù)) | 參數(shù)位2(定長類型) | 參數(shù)位3(變長數(shù)據(jù)) |
-
//| param1Len | param1Data | param3Data | param3Len | param3Data |
*/
class ProtocolType {
const TYPE_TINYINT = 'tinyint';
const TYPE_INT16 = 'int16';
const TYPE_INT32 = 'int32';
const TYPE_INT64 = 'int64';
const TYPE_STRING = 'string';
const TYPE_TEXT = 'text';/**
- 數(shù)據(jù)類型是否為定長
*/
const TYPE_FIXED_LEN = [
self::TYPE_TINYINT => true,
self::TYPE_INT16 => true,
self::TYPE_INT32 => true,
self::TYPE_INT64 => true,
self::TYPE_STRING => false,
self::TYPE_TEXT => false,
];
// 定長數(shù)據(jù)類型的字節(jié)數(shù) paramBytes = dataBytes
const TYPE_FIXED_LEN_BYTES = [
self::TYPE_TINYINT => 1, // tinyint 固定1字節(jié) 不需要長度表征 追求極致
self::TYPE_INT16 => 2, // int16 固定2字節(jié) 不需要長度表征 追求極致
self::TYPE_INT32 => 4, // int32 固定4字節(jié) 不需要長度表征 追求極致
self::TYPE_INT64 => 8, // int64 固定8字節(jié) 不需要長度表征 追求極致
];/**
- 變長數(shù)據(jù)類型長度位字節(jié)數(shù) paramBytes = dataLenBytes . dataBytes
*/
const TYPE_VARIABLE_LEN_BYTES = [
self::TYPE_STRING => 1, // string 用 1bytes 表征數(shù)據(jù)長度 0 ~ 255 個(gè)字符長度
self::TYPE_TEXT => 4, // text 用 4bytes 表征數(shù)據(jù)長度 能表征 2 ^ 32 - 1個(gè)字符長度 1PB的數(shù)據(jù) 噗
];
/**
- 數(shù)據(jù)類型對應(yīng)的打包方式
*/
const TYPE_PACK_SYMBOL = [
self::TYPE_TINYINT => 'C', // tinyint 固定1字節(jié) 不需要長度表征 追求極致 無符號(hào)字節(jié)
self::TYPE_INT16 => 'n', // int16 固定2字節(jié) 不需要長度表征 追求極致 大端無符號(hào)短整形
self::TYPE_INT32 => 'N', // int32 固定4字節(jié) 不需要長度表征 追求極致 大端無符號(hào)整形
self::TYPE_INT64 => 'J', // int64 固定8字節(jié) 不需要長度表征 追求極致 大端無符號(hào)長整形
self::TYPE_STRING => 'C', // string 用 1bytes 表征數(shù)據(jù)長度 0 ~ 255 個(gè)字符長度
self::TYPE_TEXT => 'N', // text 用 4bytes 表征數(shù)據(jù)長度 能表征 2 ^ 32 - 1個(gè)字符長度 1PB的數(shù)據(jù) 噗
];
/**
- 是否為定長類型
- @param [type] $type [description]
- @return boolean [description]
*/
public static function isFixedLenType(type];
}
/**
- 定長獲得字節(jié)數(shù)
- 變長獲得數(shù)據(jù)長度為字節(jié)數(shù)
- @param [type] $type [description]
- @return [type] [description]
*/
public static function getTypeOrTypeLenBytes(type)) {
return self::TYPE_FIXED_LEN_BYTES[type];
}
}
/**
打包二進(jìn)制數(shù)據(jù)
@param [type] $data [description]
@param [type] $paramType [description]
-
@return [type] [description]
*/
public static function pack(paramType) {
paramType];
if (self::isFixedLenType(paramProtocDataBin = pack(data);
} else {
// 變長類型 數(shù)據(jù)長度位 + 數(shù)據(jù)位
packSymbol, strlen(data;
}return $paramProtocDataBin;
}
/**
解包二進(jìn)制數(shù)據(jù)
@param [type] &$dataBin [description]
@param [type] $paramType [description]
-
@return [type] [description]
*/
public static function unPack(¶mType) {
paramType];// 定長數(shù)據(jù)直接讀取對應(yīng)的字節(jié)數(shù)解包
if (self::isFixedLenType(paramBytes = self::TYPE_FIXED_LEN_BYTES[paramBin = substr(paramBytes);
// 定長類型 直接打包數(shù)據(jù)至相應(yīng)的二進(jìn)制
packSymbol, typeLenBytes = self::TYPE_VARIABLE_LEN_BYTES[paramLenBytes = substr(typeLenBytes);
// 解析二進(jìn)制的數(shù)據(jù)長度
packSymbol, paramData = substr(typeLenBytes, paramBytes = paramDataLen;
}// 剩余待處理的數(shù)據(jù)
dataBin, $paramBytes);return $paramData;
}
}
- 數(shù)據(jù)類型是否為定長
/**
-
協(xié)議消息體
/
class ProtocolMessage {
/*- 二進(jìn)制協(xié)議流
- @var [type]
*/
public $dataBin;
/**
- [paramName1, paramName2, paramName3]
- @var array
*/
public static $paramNameMapping = [];
/**
- paramName => ProtocolType
- @var array
*/
public static $paramProtocolTypeMapping = [];
/**
- 獲取參數(shù)的協(xié)議數(shù)據(jù)類型
- @param [type] $param [description]
- @return [type] [description]
*/
public static function getParamType(paramProtocolTypeMapping[$param];
}
/**
按參數(shù)位序依次打包
-
@return [type] [description]
*/
public function packToBinStream() {
// 按參數(shù)位序
foreach (static::key => this->dataBin .= paramName . 'Bin'};
}return $this->dataBin;
}
/**
- 按參數(shù)位序一次解包
- @param [type] $dataBin [description]
- @return [type] [description]
*/
public function unpackFromBinStream(paramNameMapping as paramName) {
paramName);
paramName} = ProtocolType::unPack(paramType);
}
}
}</pre>
得到消息協(xié)議包
<pre class="php hljs language-php" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><?php
class RegisterRequest extends ProtocolMessage {
public password;
public $age;
// 參數(shù)項(xiàng)位序 accoutBin PaaswordBin ageBin
public static $paramNameMapping = [
0 => 'account',
1 => 'password',
2 => 'age',
];
// 參數(shù)類型
public static $paramProtocolTypeMapping = [
'account' => ProtocolType::TYPE_STRING,
'password' => ProtocolType::TYPE_STRING,
'age' => ProtocolType::TYPE_TINYINT,
];
public function setAccount($account) {
$paramType = static::getParamType('account');
$this->accountBin = ProtocolType::pack($account, $paramType);
}
public function getAccount() {
return $this->account;
}
public function setPassword($password) {
$paramType = static::getParamType('password');
$this->passwordBin = ProtocolType::pack($password, $paramType);
}
public function getPassword() {
return $this->password;
}
public function setAge($age) {
$paramType = static::getParamType('age');
$this->ageBin = ProtocolType::pack($age, $paramType);
}
public function getAge() {
return $this->age;
}
}
</pre>
打包至二進(jìn)制
<pre class="php hljs language-php" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><?php
$data = [
'account' => 'sqrtcat',
'password' => '123456',
'age' => 29,
];
// 文本表單
var_dump(http_build_query(data));
// 二進(jìn)制協(xié)議
registerRequest->setAccount('sqrtcat');
registerRequest->setAge(29);
registerRequest->packToBinStream();
var_dump($dataBin);
// 解析二進(jìn)制協(xié)議
dataBin);
echo registerRequest->getPassword() . PHP_EOL;
echo $registerRequest->getAge() . PHP_EOL;</pre>
數(shù)據(jù)解析
開始解析數(shù)據(jù):
- 按協(xié)議約定悍募,第一個(gè)參數(shù)項(xiàng)位是
account
, 類型是string
蘑辑,用 1byte 表示數(shù)據(jù)長度,讀取 1byte 獲取account
的長度坠宴,再讀取相應(yīng)的長度洋魂,獲得account
的數(shù)據(jù)內(nèi)容,參數(shù)項(xiàng)1解析完成喜鼓。 - 按協(xié)議約定副砍,第二個(gè)參數(shù)項(xiàng)位是
password
,類型是string
庄岖,用 1byte 表示數(shù)據(jù)長度豁翎,讀取 1byte 獲取password
的長度,再讀取相應(yīng)的長度隅忿,獲得password
的數(shù)據(jù)內(nèi)容心剥,參數(shù)項(xiàng)2解析完成。 - 按協(xié)議約定背桐,第三個(gè)參數(shù)項(xiàng)位是
age
优烧,類型是tinyint
,固定1byte链峭,讀取 1byte 獲得age
的數(shù)據(jù)內(nèi)容畦娄,參數(shù)項(xiàng)3解析完成。
大概的機(jī)制就是這樣弊仪,所以我們發(fā)送端和接收端都需要載入 protobuf
生成的數(shù)據(jù)協(xié)議包熙卡,用來解析和映射。
protobuf
類的數(shù)據(jù)打包成二進(jìn)制的方式励饵,要更多的考慮到大量變長數(shù)據(jù)的場景驳癌,如果死板的固定每個(gè)數(shù)據(jù)項(xiàng)的字節(jié)數(shù),可能會(huì)帶來一定的數(shù)據(jù)冗余
1役听、解決死板固定字段長度造成的數(shù)據(jù)填充過多的問題
為每個(gè)字段加一個(gè)長度位颓鲜,表征后面多少字節(jié)為數(shù)據(jù)位
<pre class="protobuf hljs language-protobuf" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">|1byteLen | account | 1byteLen| password |
| 7 | account | 6 | password |
|0000 0111|s|q|r|t|c|a|t|0000 0110|1|2|3|4|5|6|</pre>
但還是不夠完美:
- 長度位不夠靈活,例子中固定用1bytes去表示禾嫉,那萬一數(shù)據(jù)長度超過 255 了呢,最好有一個(gè)約定蚊丐,定義好某參數(shù)的長度位的bytes數(shù)熙参。
- '123456'占了 6bytes, 如果我打包至定長的短整型,2bytes就可以表示出來麦备,而且短整型就是定長的孽椰,我只需要知道我第二個(gè)參數(shù)是短整型就好昭娩,不需要使用長度標(biāo)識(shí)位來記錄。
所以黍匾,消息協(xié)議就應(yīng)邀而出了栏渺。
2、解決長度位固定導(dǎo)致場景受限的問題
我們需要一個(gè)協(xié)議锐涯,突出兩點(diǎn):
1磕诊、某個(gè)參數(shù)的協(xié)議結(jié)構(gòu)是怎樣的,根據(jù)字段類型纹腌,分配不同的字段協(xié)議霎终,比如變長的字符串,結(jié)構(gòu)要以 paramBytes = lenBytes + dataBytes
的方式升薯,定長的數(shù)值型莱褒,則以 paramBytes = dataBytes
。
2涎劈、參數(shù)項(xiàng)的位序與數(shù)據(jù)類型的映射關(guān)系广凸,要能確定第N個(gè)參數(shù)的字段協(xié)議結(jié)構(gòu)是怎樣的,字符串則讀取相應(yīng)的長度字節(jié)位蛛枚,再向后讀取長度個(gè)字節(jié)谅海,獲得數(shù)據(jù),定長的數(shù)值型則直接讀取相應(yīng)的固定的字節(jié)數(shù)坤候,即可獲得數(shù)據(jù)胁赢。
pack/unpack
<pre class="hljs language-scss" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">a 以NUL字節(jié)填充字符串空白
A 以SPACE(空格)填充字符串
h 十六進(jìn)制字符串,低位在前
H 十六進(jìn)制字符串白筹,高位在前
c 有符號(hào)字符 -128 ~ 127
C 無符號(hào)字符 0 ~ 255
s 有符號(hào)短整型(16位智末,主機(jī)字節(jié)序)
S 無符號(hào)短整型(16位,主機(jī)字節(jié)序)
n 無符號(hào)短整型(16位徒河,大端字節(jié)序)
v 無符號(hào)短整型(16位系馆,小端字節(jié)序)
i 有符號(hào)整型(機(jī)器相關(guān)大小字節(jié)序)
I 無符號(hào)整型(機(jī)器相關(guān)大小字節(jié)序)
l 有符號(hào)整型(32位,主機(jī)字節(jié)序) -2147483648 ~ 2147483647
L 無符號(hào)整型(32位顽照,主機(jī)字節(jié)序) 0 ~ 4294967296
N 無符號(hào)整型(32位由蘑,大端字節(jié)序)
V 無符號(hào)整型(32位,小端字節(jié)序)
q 有符號(hào)長整型(64位代兵,主機(jī)字節(jié)序)
Q 無符號(hào)長整型(64位尼酿,主機(jī)字節(jié)序) 0 ~ 18446744073709551616
J 無符號(hào)長整型(64位,大端字節(jié)序)
P 無符號(hào)長整型(64位植影,小端字節(jié)序)
f 單精度浮點(diǎn)型(機(jī)器相關(guān)大小)
d 雙精度浮點(diǎn)型(機(jī)器相關(guān)大小)
x NUL字節(jié)
X 回退一字節(jié)
Z 以NUL字節(jié)填充字符串空白(new in PHP 5.5)
@ NUL填充到絕對位置</pre>
二進(jìn)制數(shù)據(jù)壓縮
<pre class="php hljs language-php" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><?php
$raw = "69984567982132123122231";
echo "raw data: " . raw) . PHP_EOL;
$segmentRaw = [];
while (true) {
$offset = 3;
if (strlen($raw) < 3) {
$segmentRaw[] = $raw;
break;
}
$rawEle = substr($raw, 0, $offset);
if (intval($rawEle) > 255) {
$offset = 2;
$rawEle = substr($raw, 0, $offset);
}
$segmentRaw[] = $rawEle;</pre>