高效能系統中,將結構化資料(如用戶狀態)序列化為緊湊二進位格式存入 Memcached,單筆僅 52 位元組,大幅降低記憶體佔用與網路傳輸延遲。
| 欄位 | 型別 | 長度 | 偏移 | 說明 |
|---|---|---|---|---|
| User ID | Uint32 | 4 | 0-3 | 無號 32 位整數 |
| Type | Uint8 | 1 | 4 | 用戶類型枚舉 |
| State | Uint8 | 1 | 5 | 在線狀態枚舉 |
| Server IP | Uint32 | 4 | 6-9 | IPv4 整數形式 |
| Last Active | Uint64 | 8 | 10-17 | Unix 時間戳毫秒/秒 |
| Session ID Len | Uint16 | 2 | 18-19 | Session ID 實際長度 |
| Session ID | Char[32] | 32 | 20-51 | 固定 32 位元組緩衝區 |
總計:4+1+1+4+8+2+32 = 52 位元組
<?php
$mem = new Memcached();
$mem->addServer('192.168.0.69', 11211);
// 取得原始二進位字串(含可能的 null 位元組)
$raw = $mem->get('state#105709');
if ($raw === false) {
die('Key 不存在或讀取失敗');
}
// 驗證長度
if (strlen($raw) < 52) {
die('資料長度不足,可能損毀');
}
// --- 解析固定欄位 ---
// 使用 unpack 解析二進位格式(大端序 network byte order)
$header = unpack('NuserId/Ctype/Cstate/NserverIp/QlastActive/nsessionLen', substr($raw, 0, 20));
// 注意:PHP unpack Q (unsigned long long) 需 PHP 5.6.3+,舊版可拆兩個 N 手動組合
$userId = $header['userId'];
$type = $header['type'];
$state = $header['state'];
$serverIpInt = $header['serverIp'];
$lastActive = $header['lastActive'];
$sessionLen = $header['sessionLen'];
// IP 整數轉點分十進位
$ip = sprintf('%d.%d.%d.%d',
($serverIpInt >> 24) & 0xFF,
($serverIpInt >> 16) & 0xFF,
($serverIpInt >> 8) & 0xFF,
$serverIpInt & 0xFF
);
// Session ID 變長讀取(最多 32 位元組)
$sessionId = substr($raw, 20, min($sessionLen, 32));
// 結果輸出
var_dump([
'userId' => $userId,
'type' => $type,
'state' => $state,
'serverIp' => $ip,
'lastActive' => $lastActive,
'sessionId' => $sessionId,
]);
?>
PHP 字串是「位元組序列 + 長度」結構(非 C 風格以 null 結尾),Memcached::get() 回傳完整二進位資料,strlen() 正確回傳 53(含結尾 null),不會因中間出現 0x00 而截斷。
unpack 格式字元對照表| 代碼 | 型別 | 長度 | 備註 |
|---|---|---|---|
| N | Unsigned 32-bit (Big Endian) | 4 | 網路位元組序標準 |
| C | Unsigned 8-bit | 1 | 同 unsigned char |
| n | Unsigned 16-bit (Big Endian) | 2 | |
| Q | Unsigned 64-bit (Big Endian) | 8 | PHP 5.6.3+,舊版用兩個 N 組合 |
$high = unpack('N', substr($raw, 10, 4))[1];
$low = unpack('N', substr($raw, 14, 4))[1];
$lastActive = ($high << 32) | $low; // 需 64 位元整數支援或用 BCMath
確保大端序、無填充對齊、固定長度,例如 Go:
buf := make([]byte, 52) binary.BigEndian.PutUint32(buf[0:4], userId) buf[4] = userType buf[5] = state binary.BigEndian.PutUint32(buf[6:10], ipInt) binary.BigEndian.PutUint64(buf[10:18], lastActive) binary.BigEndian.PutUint16(buf[18:20], uint16(len(sessionID))) copy(buf[20:], sessionID) // 自動截斷或補零 memcached.Set(key, buf, expiry)
file_put_contents('debug.bin', $raw);
// 使用 xxd / WinHex / HxD 開啟 debug.bin
// 輸出範例:
// ED 9C 01 00 00 01 C0 A8 00 46 3E F6 0A 52 00 00 00 01 00 38 6A 57 46 6D 73 49 4B 34 31 6B 42 44 6B 6D 6C 71 43 37 6D 37 51 6F 57 49 43 51 38 6E 7A 7A 37 00
// 對應:UserID=0x00019CED, Type=0, State=1, IP=192.168.0.70, LastActive=0x00000001520AF63E, SessionLen=0x38(56), SessionID=...
unpack 解析固定頭部,substr 取變長尾部。