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

PHP pack/unpack 處理二進制檔案完整指南

     編輯:PHP 綜合知識

核心概念

pack():依格式字串將資料「打包」成二進位字串。
unpack():依格式字串將二進位字串「解包」為陣列。
兩者是 PHP 處理二進制協議、檔案格式、網路封包、硬體介接的基礎工具。

格式字元完整對照表

代碼說明位元組序大小範例
aNUL 填充字串-指定長度pack('a10', 'hi') → "hi\0\0\0\0\0\0\0\0"
A空白填充字串-指定長度pack('A10', 'hi') → "hi "
h十六進位字串 (低半位元組優先)--pack('h*', 'abc')
H十六進位字串 (高半位元組優先)--pack('H*', 'abc')
c有號字元-1 byte-128 ~ 127
C無號字元-1 byte0 ~ 255
s有號短整數機器序2 bytes-32768 ~ 32767
S無號短整數機器序2 bytes0 ~ 65535
n無號短整數Big Endian (網路序)2 bytes跨平台安全
v無號短整數Little Endian2 bytesIntel x86 原生
i有號整數機器相關機器相關通常 4 bytes
I無號整數機器相關機器相關通常 4 bytes
l有號長整數機器序4 bytes-2^31 ~ 2^31-1
L無號長整數機器序4 bytes0 ~ 2^32-1
N無號長整數Big Endian4 bytes網路協議標準
V無號長整數Little Endian4 bytesWindows/Intel 常用
f單精度浮點數機器相關4 bytesIEEE 754
d雙精度浮點數機器相關8 bytesIEEE 754
xNUL 位元組 (填充)-1 byte對齊用
X回退 1 位元組--罕用
@絕對位置填充 NUL--pack('@4C', 65) → 第 5 位元組為 'A'
P指標結構 (平台相關)--極度危險,慎用
Z以 NUL 結尾字串--同 a 但保證結尾 NUL

重複計數與組合

// 重複計數
pack('C4', 1, 2, 3, 4)    // 等同 pack('C*', 1, 2, 3, 4)
pack('N*', 1, 2, 3)       // * 表示消耗所有剩餘參數

// 組合結構
$binary = pack('NVC*', 0x12345678, 0x9ABC, 'Hello'); 
// Big Endian uint32 + Little Endian uint16 + 字串

實戰範例 1:自訂二進制日誌格式

/**
 * 寫入結構化二進制日誌
 * 格式:[時間戳 uint32 BE][等級 uint8][訊息長度 uint16 BE][訊息內容]
 */
function writeBinaryLog(string $file, string $level, string $message): void {
    $levelMap = ['DEBUG' => 1, 'INFO' => 2, 'WARN' => 3, 'ERROR' => 4];
    $levelByte = $levelMap[$level] ?? 0;
    $msgLen = strlen($message);
    
    if ($msgLen > 65535) throw new LengthException('訊息過長');
    
    $binary = pack('N C n a*', time(), $levelByte, $msgLen, $message);
    file_put_contents($file, $binary, FILE_APPEND | LOCK_EX);
}

/**
 * 讀取並解析二進制日誌
 */
function readBinaryLog(string $file): Generator {
    $handle = fopen($file, 'rb');
    if (!$handle) return;
    
    while (!feof($handle)) {
        // 先讀固定頭部 7 bytes (4+1+2)
        $header = fread($handle, 7);
        if (strlen($header) < 7) break; // 不完整記錄
        
        [$timestamp, $levelByte, $msgLen] = unpack('Ntimestamp/Clevel/nlen', $header);
        
        $message = fread($handle, $msgLen);
        if (strlen($message) < $msgLen) break;
        
        $levelStr = array_flip(['DEBUG' => 1, 'INFO' => 2, 'WARN' => 3, 'ERROR' => 4])[$levelByte] ?? 'UNKNOWN';
        
        yield [
            'timestamp' => $timestamp,
            'level'     => $levelStr,
            'message'   => $message,
        ];
    }
    fclose($handle);
}

// 使用
writeBinaryLog('/var/log/app.bin', 'INFO', 'User login: john_doe');
foreach (readBinaryLog('/var/log/app.bin') as $entry) {
    echo date('Y-m-d H:i:s', $entry['timestamp']) . " [{$entry['level']}] {$entry['message']}\n";
}

實戰範例 2:網路封包構造 (TCP/UDP 標頭)

/**
 * 構造簡易 TCP 封包 (僅示範結構,實際需計算校驗和)
 */
function buildTcpPacket(
    int $srcPort, int $dstPort, 
    int $seqNum, int $ackNum,
    int $flags, // FIN=1, SYN=2, RST=4, PSH=8, ACK=16, URG=32
    int $windowSize,
    string $payload = ''
): string {
    // TCP Header 格式 (RFC 793)
    // Source Port (2) | Dest Port (2) | Seq Num (4) | Ack Num (4)
    // Data Offset (4 bits) | Reserved (6 bits) | Flags (6 bits) | Window (2)
    // Checksum (2) | Urgent Pointer (2) | Options (variable) | Payload
    
    $dataOffset = 5; // 5 * 4 = 20 bytes, 無選項
    $reserved = 0;
    $offsetFlags = ($dataOffset << 12) | ($reserved << 6) | $flags;
    
    // 校驗和暫設 0,實際需計算偽首部 + TCP 首部 + 負載
    $checksum = 0;
    $urgentPtr = 0;
    
    $header = pack('n n N N n n n n', 
        $srcPort, $dstPort, $seqNum, $ackNum,
        $offsetFlags, $windowSize, $checksum, $urgentPtr
    );
    
    return $header . $payload;
}

// 發送原始封包需 root 權限 + socket_create(AF_INET, SOCK_RAW, IPPROTO_TCP)
// 此處僅示範 pack 結構

實戰範例 3:高效能二進制快取 (替代 JSON/serialize)

/**
 * 簡易二進制序列化 (固定結構陣列)
 * 結構:[筆數 uint32][每筆: ID uint64][分數 float64][時間戳 uint32]
 */
function serializeScores(array $records): string {
    $count = count($records);
    $binary = pack('N', $count);
    foreach ($records as $rec) {
        $binary .= pack('J d N', $rec['id'], $rec['score'], $rec['time']);
    }
    return $binary;
}

function deserializeScores(string $binary): array {
    $offset = 0;
    $count = unpack('N', substr($binary, $offset, 4))[1];
    $offset += 4;
    
    $records = [];
    for ($i = 0; $i < $count; $i++) {
        $chunk = substr($binary, $offset, 20); // 8+8+4
        [$id, $score, $time] = unpack('Jid/dscore/Ntime', $chunk);
        $records[] = ['id' => $id, 'score' => $score, 'time' => $time];
        $offset += 20;
    }
    return $records;
}

// 效能測試:10萬筆資料
$records = [];
for ($i = 0; $i < 100000; $i++) {
    $records[] = ['id' => $i, 'score' => mt_rand(0, 10000)/100, 'time' => time()];
}

$start = microtime(true);
$bin = serializeScores($records);
$serializeTime = microtime(true) - $start;

$start = microtime(true);
$decoded = deserializeScores($bin);
$deserializeTime = microtime(true) - $start;

echo "二進制序列化: {$serializeTime}s, 反序列化: {$deserializeTime}s, 大小: " . strlen($bin) . " bytes\n";
// 對比 json_encode/decode 通常快 3-5 倍,體積小 50%+

常見坑點與最佳實踐

  1. 位元組序一致性:跨平台/語言交互必須使用 N/n (Big Endian) 或 V/v (Little Endian),禁用 L/S/I/l/i (機器相關)。
  2. 字串長度:a/A 需指定固定長度;變長字串用 n/a* (先寫長度再寫內容) 或 Z (NUL 結尾)。
  3. 浮點數精度:f/d 遵循 IEEE 754,跨語言通常相容,但極大/極小值需測試。
  4. 記憶體限制:處理大檔案時使用 fread/fwrite 串流,避免 file_get_contents 載入整個檔案。
  5. unpack 回傳鍵名:unpack('Nid', $bin) 回傳 ['id' => 123];若未命名 unpack('N', $bin) 回傳數字鍵 [1 => 123]。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved