首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 二進制流處理實戰:pack、unpack、ord 深度解析

     編輯:PHP 綜合知識

前言

在物聯網通訊、支付網關對接、舊系統整合等場景,PHP 需直接操作二進制位元組流。pack()、unpack()、ord() 是 PHP 處理二進制資料的三大核心函數,本文透過完整實例,從格式字元對照表到實戰通訊協定封裝/解析,帶你掌握這門「冷門但關鍵」的技能。

核心函數速查

pack() — 將資料打包為二進制字串

string pack ( string $format, mixed ...$values )

常用格式字元(完整表見 PHP 官方文檔):

字元說明位元組數位序
a以 NULL 填充字串依長度-
A以空格填充字串依長度-
c有號字元1-
C無號字元1-
s有號短整數2主機序
n無號短整數2大端序
v無號短整數2小端序
i有號整數4/8主機序
I無號整數4/8主機序
l有號長整數4主機序
N無號長整數4大端序
V無號長整數4小端序
f單精度浮點數4主機序
d雙精度浮點數8主機序

unpack() — 從二進制字串解包資料

array unpack ( string $format, string $data [, int $offset = 0 ] )

格式字元與 pack() 相同,回傳關聯陣列,鍵名可自訂(如 "username/A20")。

ord() — 取得字元 ASCII/位元組值

int ord ( string $character )

互動式格式驗證 Demo

以下程式碼展示相同數值用不同格式打包的位元組差異:

<?php
function getAscill($str) {
    foreach (str_split($str) as $v) {
        echo $v . "=" . ord($v) . "\n";
    }
    echo "=============\n\n";
}

// A 字串(空格填充)
$str = pack("A*", "中國");
echo $str . "=" . strlen($str) . " 位元組\n";
getAscill($str);

// H 十六進位(高位在前)
$str = pack("H*", "fffe");
echo $str . "=" . strlen($str) . " 位元組\n";
getAscill($str);

// C 無號字元
$str = pack("C*", 55, 56, 57);
echo $str . "=" . strlen($str) . " 位元組\n";
getAscill($str);

// i 有號整數(32/64 位視平台)
$str = pack("i", 100);
echo $str . "=" . strlen($str) . " 位元組\n";
getAscill($str);

// s 短整數(2 位元組)
$str = pack("s", 100);
echo $str . "=" . strlen($str) . " 位元組\n";
getAscill($str);

// l 長整數(4 位元組)
$str = pack("l", 100);
echo $str . "=" . strlen($str) . " 位元組\n";
getAscill($str);

// f 單精度浮點
$str = pack("f", 100);
echo $str . "=" . strlen($str) . " 位元組\n";
getAscill($str);

// d 雙精度浮點
$str = pack("d", 100);
echo $str . "=" . strlen($str) . " 位元組\n";
getAscill($str);
?>

實戰案例:自定義通訊協定封裝/解析

某舊系統要求 TCP 短連線,負載結構如下:

欄位長度型別格式字元
用戶名20 位元組字串A20
密碼10 位元組字串A10
年齡1 位元組無號字元C
出生年月4 位元組無號長整數(大端序)N
郵箱50 位元組字串A50

欄位間以 \0 分隔,總長 89 位元組。

A 端:封包發送

<?php
$code = array(
    "username" => array("A20", "張三adfb12"),
    "pass"     => array("A10", "asdf*#1"),
    "age"      => array("C", 23),
    "birthday" => array("N", 19900101), // 大端序無號長整數
    "email"    => array("A50", "zhangsan@163.com")
);

function packByArr($arr) {
    $out = [];
    foreach ($arr as $v) $out[] = pack($v[0], $v[1]);
    return $out;
}

$stream = join("\0", packByArr($code));
echo "封包長度: " . strlen($stream) . " 位元組\n";
// file_put_contents("packet.bin", $stream); // 寫檔或 socket 發送
?>

B 端:解包還原

<?php
$code = array(
    "username" => "A20",
    "pass"     => "A10",
    "age"      => "C",
    "birthday" => "N",
    "email"    => "A50"
);

$stream = file_get_contents("packet.bin");
$fields = explode("\0", $stream);

$result = [];
$i = 0;
foreach ($code as $k => $fmt) {
    // unpack 回傳陣列,鍵名為 1,2... 需手動映射
    $unpacked = unpack($fmt, $fields[$i]);
    $result[$k] = array_shift($unpacked); // 取第一個值
    $i++;
}

print_r($result);
/* 輸出範例:
Array
(
    [username] => 張三adfb12
    [pass]     => asdf*#1
    [age]      => 23
    [birthday] => 19900101
    [email]    => zhangsan@163.com
)
*/
?>

資料庫設計啟示

文中提到:INT(10) 僅佔 4 位元組,而 CHAR(10) 佔 10 位元組(不含索引開銷)。正確選擇欄位型別可節省 50%+ 儲存空間,並大幅提升索引效能。例如:

  • 狀態碼、枚舉值 → TINYINT UNSIGNED (1 byte)
  • 年份、ID → SMALLINT/MEDIUMINT/INT UNSIGNED
  • 精確金額 → DECIMAL(10,2) 而非 FLOAT
  • 固定長度代碼 → CHAR;變長文本 → VARCHAR

常見坑點與避雷指南

  1. 位序不一致:跨平台通訊強制指定 n/N (大端) 或 v/V (小端),禁用 s/i/l 等主機序格式。
  2. 字串長度超限:pack("A10", "超過10字元字串") 會截斷,解包時補空格導致資料髒污。發送前必須 mb_strlen() 校驗。
  3. PHP 整數溢位:32 位系統 pack("N", 4000000000) 會溢位,建議統一 64 位環境或用 bcmath 處理大數。
  4. 浮點數精度:pack("f", 0.1) 非精確值,金融嚴禁用浮點;改用定點整數(分為單位)或 DECIMAL 字串傳輸。

總結

pack/unpack/ord 是 PHP 處理二進制協定的「瑞士刀」。雖然現代 API 多採 JSON/Protobuf,但在舊系統整合、硬體通訊、支付 ISO8583、自定義二進制協定等場景仍不可替代。掌握格式字元、位序、長度校驗三大核心,即可游刃有餘地處理任何位元組流挑戰。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved