在PHP开发中,有时我们需要将多个数据按指定格式打包成二进制字符串,方便网络传输或文件存储。PHP提供的pack函数可以实现这个需求,但它需要传入复杂的格式字符串,不同场景格式差异较大。本文将介绍如何用pack函数封装一个支持多种格式的通用打包函数,提升代码的复用性和灵活性。
pack函数的基本语法是:
string pack(string $format, mixed ...$values)
$format 指定格式,如C表示无符号字符,n表示16位大端字节序整型等。
$values 是对应格式的数据。
例如,将两个无符号字符和一个16位大端整数打包:
$data = pack('CCn', 0x01, 0x02, 0x1234);
我们希望封装的函数:
接受格式字符串和对应的值数组。
自动根据格式字符串将值逐个打包。
支持常见格式类型,且方便扩展。
处理参数和格式不匹配的错误。
示例函数签名:
function universalPack(string $format, array $values): string
下面是一个基础实现,支持C、n、N、a等格式:
function universalPack(string $format, array $values): string {
$result = '';
$formatLength = strlen($format);
$valueIndex = 0;
for ($i = 0; $i < $formatLength; $i++) {
$char = $format[$i];
$repeat = 1;
// 检测是否有重复数量,如a4、C2等
if (isset($format[$i + 1]) && ctype_digit($format[$i + 1])) {
$repeat = (int)$format[$i + 1];
$i++;
}
for ($r = 0; $r < $repeat; $r++) {
if (!isset($values[$valueIndex])) {
throw new InvalidArgumentException("值数量与格式不匹配");
}
$value = $values[$valueIndex];
$valueIndex++;
switch ($char) {
case 'C': // 无符号字符
$result .= pack('C', $value);
break;
case 'n': // 16位大端字节序
$result .= pack('n', $value);
break;
case 'N': // 32位大端字节序
$result .= pack('N', $value);
break;
case 'a': // NUL填充字符串,长度由repeat决定
if ($repeat < 1) {
throw new InvalidArgumentException("a格式需要指定长度");
}
$str = str_pad(substr($value, 0, $repeat), $repeat, "\0");
$result .= $str;
// a格式一次处理所有repeat,valueIndex不递增
$valueIndex--;
break;
default:
throw new InvalidArgumentException("不支持的格式字符: $char");
}
}
}
return $result;
}
假设要打包一个数据包,结构为:
1字节命令号(C)
2字节数据长度(n)
4字节数据ID(N)
10字节数据内容(字符串,a10)
调用示例:
$command = 0x01;
$length = 14;
$dataId = 123456;
$content = "hello";
$packed = universalPack('CnNa10', [$command, $length, $dataId, $content]);
echo bin2hex($packed);
输出为:
01000e0001e24068656c6c6f0000000000
pack函数很强大,但直接使用时格式与值容易出错。
封装一个通用的universalPack函数,简化多格式数据的打包工作。
函数可以根据实际需求继续增强,比如支持更多格式,支持小端序等。
实际项目中,也可以结合unpack函数实现对等的解包功能。