轉(zhuǎn)載自https://segmentfault.com/a/1190000022356844
二進制編碼傳輸協(xié)議
思考
- 何為二進制協(xié)議傳輸,何為文本協(xié)議數(shù)據(jù)傳輸?
- 網(wǎng)絡(luò)編程中數(shù)據(jù)協(xié)議的制定方式有哪些?
-
Protobuf等二進制數(shù)據(jù)序列化傳輸協(xié)議的機制是什么?
在網(wǎng)絡(luò)編程中,經(jīng)??吹揭髷?shù)據(jù)要以二進制的方式進行傳輸,起初我很不理解,為什么要刻意的說明二進制方式呢?數(shù)據(jù)在底層的傳輸不都是二進制流嗎?而且還引出了 pack/unpack 方法簇。
我們經(jīng)常用到的 rpc,比如 json-rpc 是以 文本方式 傳輸序列化的數(shù)據(jù)的。grpc(protobuf), thrift 都是以 二進制方式 傳輸數(shù)據(jù)的。所以到底何為二進制傳輸呢?
大家可以先想一下日常中發(fā)送請求時經(jīng)常用到的方式: xml, json, formData,他們雖然格式不同,但都有一個特征,自帶描述信息(直白說就是攜帶 參數(shù)名),像 文本 一樣,能很直觀的看到數(shù)據(jù)表征的內(nèi)容。
如果我們事先定義了數(shù)據(jù)中的n~m個字節(jié)位固定作為某參數(shù)的數(shù)據(jù)段,就可以免去 參數(shù)名 所帶來的額外開銷。比如 0 ~ 10 字節(jié)為 account,11 ~ 24 字節(jié)為 passowrd。又因為用戶名或密碼是非定長的,而解析數(shù)據(jù)時又要根據(jù)字節(jié)位精準(zhǔn)的截取,所以我們需要對數(shù)據(jù)項進行打包填充,使其固定字節(jié)長度,而后在服務(wù)端進行解包,pack/unpack便可以實現(xiàn)此功能。
白話
tcp 協(xié)議是日常中最為常見的二進制協(xié)議,協(xié)議體的字節(jié)位都有約定好的表征。
http 在廣義上來說也是二進制模式,使用 \r\n 對協(xié)議進行解包,解析,但 http 攜帶的數(shù)據(jù)通常都是文本模式的,比如 "sqrtcat" 占了 7 個字節(jié),在文本 or 二進制模式下沒什么區(qū)別,但"29",以文本模式發(fā)送需要 2bytes,以二進制模式打包至字符類型,只需要 1bytes。
二進制為何能提高數(shù)據(jù)傳輸效率:
- 根據(jù)協(xié)議約定,省去參數(shù)名所占用的字節(jié),縮減了數(shù)據(jù)。
- 將數(shù)值類型的數(shù)據(jù)打包至相應(yīng)范圍內(nèi)的二進制,節(jié)省了空間,4bytes能表示 32 位的文本數(shù)值,但文本數(shù)據(jù)值要 32bytes。
- 在一定程度上可以起到加密數(shù)據(jù)的作用,如果第三方不知道數(shù)據(jù)協(xié)議,就沒有辦法截取相應(yīng)的字節(jié)為獲取數(shù)據(jù),或得到數(shù)據(jù)的表征。
文本方式傳輸
日常開發(fā),比如發(fā)送一個用戶注冊 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ù)體重得到各項參數(shù)。
二進制傳輸方式
二進制傳輸,離不開協(xié)議的制定。文本方式傳輸?shù)臄?shù)據(jù)可以自我描述,而二進制方式傳輸?shù)臄?shù)據(jù),需要通過協(xié)議進行解析和讀取。
最簡單的,參數(shù)定長的方式,account 固定為 11 位,password 固定為 14 位,使用 pack將數(shù)據(jù)填充至相應(yīng)的協(xié)議長度,發(fā)送,服務(wù)端按協(xié)議進行字節(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ù)進行填充 php 解包時會自動 trim 掉
// a 以 0x00 字符對數(shù)據(jù)進行填充 php 解包時會保留 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é)議和二進制傳輸?shù)姆绞较?,只用?25bytes。這就滿足了?并不能夠~,這種簡單協(xié)議的二進制傳輸方式只是在一定場景下發(fā)揮了傳輸效率,在某些場景下可能還不如文本方式。因為嚴格的數(shù)據(jù)定長填充,可能會造成數(shù)據(jù)的冗余,比如 account只有一個字符 s,password 也只有一個字符 1,在此協(xié)議下還是固定 25bytes,文本傳輸反而效率會高一些。
二進制傳輸敗北了?No,是我們協(xié)議太簡單,不夠靈活,沒有最大程度上發(fā)揮協(xié)議+二進制的高效性,可以說,協(xié)議下的二進制傳輸方式,能做到絕對的高效于文本傳輸,這里我們可以簡單的分析和模擬以二進制方式傳輸?shù)?protobuf 的協(xié)議模式。
Protobuf 的二進制傳輸
我們可以簡單分析下 protobuf傳輸數(shù)據(jù)的方式:
- 定義
IDL,其實就相當(dāng)于制定了協(xié)議體 - 生成
proto文件,得到具體的消息字段的參數(shù)項位和參數(shù)長度位映射的消息協(xié)議包。 - 發(fā)送端根據(jù)消息協(xié)議定義的參數(shù)數(shù)據(jù)類型(主要是變長 or 定長),將數(shù)據(jù)打包至相應(yīng)的二進制格式。
- 發(fā)送數(shù)據(jù)。
- 接收端按消息協(xié)議格式對二進制數(shù)據(jù)進行解析,獲得文本數(shù)據(jù)。
這里原諒我自己造了兩個詞,參數(shù)項位 和 參數(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 二進制序列化組件,完整版已放置 github 支持的數(shù)據(jù)類型還是很全面的:protoBin。
協(xié)議數(shù)據(jù)類型的二進制格式約定
主要是定義哪些類型是定長,哪些類型是變長,變長類型還需給定長度位的字節(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 個字符長度
self::TYPE_TEXT => 4, // text 用 4bytes 表征數(shù)據(jù)長度 能表征 2 ^ 32 - 1個字符長度 1PB的數(shù)據(jù) 噗
];
/**
- 數(shù)據(jù)類型對應(yīng)的打包方式
*/
const TYPE_PACK_SYMBOL = [
self::TYPE_TINYINT => 'C', // tinyint 固定1字節(jié) 不需要長度表征 追求極致 無符號字節(jié)
self::TYPE_INT16 => 'n', // int16 固定2字節(jié) 不需要長度表征 追求極致 大端無符號短整形
self::TYPE_INT32 => 'N', // int32 固定4字節(jié) 不需要長度表征 追求極致 大端無符號整形
self::TYPE_INT64 => 'J', // int64 固定8字節(jié) 不需要長度表征 追求極致 大端無符號長整形
self::TYPE_STRING => 'C', // string 用 1bytes 表征數(shù)據(jù)長度 0 ~ 255 個字符長度
self::TYPE_TEXT => 'N', // text 用 4bytes 表征數(shù)據(jù)長度 能表征 2 ^ 32 - 1個字符長度 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];
}
}
/**
打包二進制數(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;
}
/**
解包二進制數(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)的二進制
packSymbol,
typeLenBytes = self::TYPE_VARIABLE_LEN_BYTES[
paramLenBytes = substr(
typeLenBytes);
// 解析二進制的數(shù)據(jù)長度
packSymbol,
paramData = substr(
typeLenBytes,
paramBytes =
paramDataLen;
}// 剩余待處理的數(shù)據(jù)
dataBin, $paramBytes);
return $paramData;
}
}
- 數(shù)據(jù)類型是否為定長
/**
-
協(xié)議消息體
/
class ProtocolMessage {
/*- 二進制協(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ù)項位序 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>
打包至二進制
<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));
// 二進制協(xié)議
registerRequest->setAccount('sqrtcat');
registerRequest->setAge(29);
registerRequest->packToBinStream();
var_dump($dataBin);
// 解析二進制協(xié)議
dataBin);
echo registerRequest->getPassword() . PHP_EOL;
echo $registerRequest->getAge() . PHP_EOL;</pre>
數(shù)據(jù)解析
開始解析數(shù)據(jù):
- 按協(xié)議約定,第一個參數(shù)項位是
account, 類型是string,用 1byte 表示數(shù)據(jù)長度,讀取 1byte 獲取account的長度,再讀取相應(yīng)的長度,獲得account的數(shù)據(jù)內(nèi)容,參數(shù)項1解析完成。 - 按協(xié)議約定,第二個參數(shù)項位是
password,類型是string,用 1byte 表示數(shù)據(jù)長度,讀取 1byte 獲取password的長度,再讀取相應(yīng)的長度,獲得password的數(shù)據(jù)內(nèi)容,參數(shù)項2解析完成。 - 按協(xié)議約定,第三個參數(shù)項位是
age,類型是tinyint,固定1byte,讀取 1byte 獲得age的數(shù)據(jù)內(nèi)容,參數(shù)項3解析完成。
大概的機制就是這樣,所以我們發(fā)送端和接收端都需要載入 protobuf 生成的數(shù)據(jù)協(xié)議包,用來解析和映射。
protobuf 類的數(shù)據(jù)打包成二進制的方式,要更多的考慮到大量變長數(shù)據(jù)的場景,如果死板的固定每個數(shù)據(jù)項的字節(jié)數(shù),可能會帶來一定的數(shù)據(jù)冗余
1、解決死板固定字段長度造成的數(shù)據(jù)填充過多的問題
為每個字段加一個長度位,表征后面多少字節(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 了呢,最好有一個約定,定義好某參數(shù)的長度位的bytes數(shù)。
- '123456'占了 6bytes, 如果我打包至定長的短整型,2bytes就可以表示出來,而且短整型就是定長的,我只需要知道我第二個參數(shù)是短整型就好,不需要使用長度標(biāo)識位來記錄。
所以,消息協(xié)議就應(yīng)邀而出了。
2、解決長度位固定導(dǎo)致場景受限的問題
我們需要一個協(xié)議,突出兩點:
1、某個參數(shù)的協(xié)議結(jié)構(gòu)是怎樣的,根據(jù)字段類型,分配不同的字段協(xié)議,比如變長的字符串,結(jié)構(gòu)要以 paramBytes = lenBytes + dataBytes 的方式,定長的數(shù)值型,則以 paramBytes = dataBytes。
2、參數(shù)項的位序與數(shù)據(jù)類型的映射關(guān)系,要能確定第N個參數(shù)的字段協(xié)議結(jié)構(gòu)是怎樣的,字符串則讀取相應(yīng)的長度字節(jié)位,再向后讀取長度個字節(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 十六進制字符串,低位在前
H 十六進制字符串,高位在前
c 有符號字符 -128 ~ 127
C 無符號字符 0 ~ 255
s 有符號短整型(16位,主機字節(jié)序)
S 無符號短整型(16位,主機字節(jié)序)
n 無符號短整型(16位,大端字節(jié)序)
v 無符號短整型(16位,小端字節(jié)序)
i 有符號整型(機器相關(guān)大小字節(jié)序)
I 無符號整型(機器相關(guān)大小字節(jié)序)
l 有符號整型(32位,主機字節(jié)序) -2147483648 ~ 2147483647
L 無符號整型(32位,主機字節(jié)序) 0 ~ 4294967296
N 無符號整型(32位,大端字節(jié)序)
V 無符號整型(32位,小端字節(jié)序)
q 有符號長整型(64位,主機字節(jié)序)
Q 無符號長整型(64位,主機字節(jié)序) 0 ~ 18446744073709551616
J 無符號長整型(64位,大端字節(jié)序)
P 無符號長整型(64位,小端字節(jié)序)
f 單精度浮點型(機器相關(guān)大小)
d 雙精度浮點型(機器相關(guān)大小)
x NUL字節(jié)
X 回退一字節(jié)
Z 以NUL字節(jié)填充字符串空白(new in PHP 5.5)
@ NUL填充到絕對位置</pre>
二進制數(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>