如果您想了解 pack/unpack。这里有一个 perl 的教程,它在理解 php 的 pack/unpack 方面同样有效
https://perldoc.perl5.cn/perlpacktut.html
(PHP 4, PHP 5, PHP 7, PHP 8)
pack — 将数据打包成二进制字符串
根据 format
将给定的参数打包成二进制字符串。
此函数的理念来自 Perl,所有格式代码的工作方式与 Perl 中相同。但是,某些格式代码缺失,例如 Perl 的“u”格式代码。
请注意,有符号和无符号值之间的区别仅影响 unpack() 函数,而 pack() 函数对有符号和无符号格式代码给出相同的结果。
format
format
字符串由格式代码和可选的重复器参数组成。重复器参数可以是整数值或 *
,用于重复到输入数据的末尾。对于 a、A、h、H,重复计数指定从一个数据参数中获取多少个字符,对于 @,它是在何处放置下一个数据的绝对位置,对于其他所有情况,重复计数指定消耗和打包到结果二进制字符串中的数据参数数量。
当前已实现的格式为
代码 | 描述 |
---|---|
a | 以 NUL 填充的字符串 |
A | 以空格填充的字符串 |
h | 十六进制字符串,低位字节在前 |
H | 十六进制字符串,高位字节在前 |
c | 有符号字符 |
C | 无符号字符 |
s | 有符号短整型(始终为 16 位,机器字节序) |
S | 无符号短整型(始终为 16 位,机器字节序) |
n | 无符号短整型(始终为 16 位,大端字节序) |
v | 无符号短整型(始终为 16 位,小端字节序) |
i | 有符号整型(机器相关的尺寸和字节序) |
I | 无符号整型(机器相关的尺寸和字节序) |
l | 有符号长整型(始终为 32 位,机器字节序) |
L | 无符号长整型(始终为 32 位,机器字节序) |
N | 无符号长整型(始终为 32 位,大端字节序) |
V | 无符号长整型(始终为 32 位,小端字节序) |
q | 有符号长长整型(始终为 64 位,机器字节序) |
Q | 无符号长长整型(始终为 64 位,机器字节序) |
J | 无符号长长整型(始终为 64 位,大端字节序) |
P | 无符号长长整型(始终为 64 位,小端字节序) |
f | 浮点数(机器相关的尺寸和表示) |
g | 浮点数(机器相关的尺寸,小端字节序) |
G | 浮点数(机器相关的尺寸,大端字节序) |
d | 双精度浮点数(机器相关的尺寸和表示) |
e | 双精度浮点数(机器相关的尺寸,小端字节序) |
E | 双精度浮点数(机器相关的尺寸,大端字节序) |
x | NUL 字节 |
X | 后退一个字节 |
Z | 以 NUL 填充的字符串 |
@ | 用 NUL 填充到绝对位置 |
values
返回包含数据的二进制字符串。
版本 | 描述 |
---|---|
8.0.0 | 此函数不再在失败时返回 false 。 |
7.2.0 | float 和 double 类型支持大端和小端。 |
7.0.15,7.1.1 | 添加了“e”、“E”、“g”和“G”代码,以支持浮点数和双精度浮点数的字节序。 |
示例 #1 pack() 示例
<?php
$binarydata = pack("nvc*", 0x1234, 0x5678, 65, 66);
?>
生成的二进制字符串将是 6 个字节长,包含字节序列 0x12、0x34、0x78、0x56、0x41、0x42。
请注意,PHP 在内部将 int 值存储为机器相关的尺寸的有符号值(C 类型 long
)。整型字面量和产生超出 int 类型范围的数字的操作将被存储为 float。将这些浮点数打包为整型时,它们首先被强制转换为整型类型。这可能会或可能不会导致所需的字节模式。
最相关的情况是打包如果 int 类型是无符号的,则可以用 int 类型表示的无符号数字。在 int 类型具有 32 位尺寸的系统中,强制转换通常会导致与 int 为无符号时相同的字节模式(尽管这依赖于根据 C 标准进行的实现定义的无符号到有符号转换)。在 int 类型具有 64 位尺寸的系统中,float 最有可能没有足够大的尾数来保存该值而不会丢失精度。如果这些系统也有本地的 64 位 C int
类型(大多数类 UNIX 系统都没有),那么在较高范围内使用 I
打包格式的唯一方法是创建具有与所需无符号值相同的字节表示的 int 负值。
如果您想了解 pack/unpack。这里有一个 perl 的教程,它在理解 php 的 pack/unpack 方面同样有效
https://perldoc.perl5.cn/perlpacktut.html
一个辅助类,用于将整型转换为二进制字符串,反之亦然。这对于写入和读取文件或套接字中的整型很有用。
<?php
class int_helper
{
public static function int8($i) {
return is_int($i) ? pack("c", $i) : unpack("c", $i)[1];
}
public static function uInt8($i) {
return is_int($i) ? pack("C", $i) : unpack("C", $i)[1];
}
public static function int16($i) {
return is_int($i) ? pack("s", $i) : unpack("s", $i)[1];
}
public static function uInt16($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // 大端序
$i = $f("n", $i);
}
else if ($endianness === false) { // 小端序
$i = $f("v", $i);
}
else if ($endianness === null) { // 机器字节序
$i = $f("S", $i);
}
return is_array($i) ? $i[1] : $i;
}
public static function int32($i) {
return is_int($i) ? pack("l", $i) : unpack("l", $i)[1];
}
public static function uInt32($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // 大端序
$i = $f("N", $i);
}
else if ($endianness === false) { // 小端序
$i = $f("V", $i);
}
else if ($endianness === null) { // 机器字节序
$i = $f("L", $i);
}
return is_array($i) ? $i[1] : $i;
}
public static function int64($i) {
return is_int($i) ? pack("q", $i) : unpack("q", $i)[1];
}
public static function uInt64($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // 大端序
$i = $f("J", $i);
}
else if ($endianness === false) { // 小端序
$i = $f("P", $i);
}
else if ($endianness === null) { // 机器字节序
$i = $f("Q", $i);
}
return is_array($i) ? $i[1] : $i;
}
}
?>
使用示例
<?php
Header("Content-Type: text/plain");
include("int_helper.php");
echo int_helper::uInt8(0x6b) . PHP_EOL; // k
echo int_helper::uInt8(107) . PHP_EOL; // k
echo int_helper::uInt8("\x6b") . PHP_EOL . PHP_EOL; // 107
echo int_helper::uInt16(4101) . PHP_EOL; // \x05\x10
echo int_helper::uInt16("\x05\x10") . PHP_EOL; // 4101
echo int_helper::uInt16("\x05\x10", true) . PHP_EOL . PHP_EOL; // 1296
echo int_helper::uInt32(2147483647) . PHP_EOL; // \xff\xff\xff\x7f
echo int_helper::uInt32("\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 2147483647
// 注意:使用 64 位版本的 PHP 进行测试
echo int_helper::uInt64(9223372036854775807) . PHP_EOL; // \xff\xff\xff\xff\xff\xff\xff\x7f
echo int_helper::uInt64("\xff\xff\xff\xff\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 9223372036854775807
?>
请注意,在 Perl 中,上面的命令看起来像这样
$binarydata = pack ("n v c*", 0x1234, 0x5678, 65, 66);
在 PHP 中,第一个参数似乎不允许空格。 因此,如果您想将 Perl 的 pack 命令转换为 PHP,请不要忘记删除空格!
如果您需要从大端序或小端序中解包一个有符号的短整型,而不是机器字节序,您只需将其解包为无符号形式,然后如果结果 >= 2^15,则从中减去 2^16。
一个例子是
<?php
$foo = unpack("n", $signedbigendianshort);
$foo = $foo[1];
if($foo >= pow(2, 15)) $foo -= pow(2, 16);
?>
/* 将浮点数从主机序转换为网络序 */
function FToN( $val )
{
$a = unpack("I",pack( "f",$val ));
return pack("N",$a[1] );
}
/* 将网络字节序的浮点数转换为主机字节序 */
function NToF($val )
{
$a = unpack("N",$val);
$b = unpack("f",pack( "I",$a[1]));
return $b[1];
}
注意格式代码 H 总是对字节对齐进行填充 0(对于奇数个半字节)。
因此 pack("H", "7") 会生成 0x70(ASCII 字符 'p'),而不是 0x07(响铃字符)
以及 pack("H*", "347") 会生成 0x34 ('4') 和 0x70 ('p'),而不是 0x03 和 0x47。
您将使用以下方法获得相同的效果
<?php
function _readInt($fp)
{
return unpack('V', fread($fp, 4));
}
?>
或 unpack('N', ...) 用于大端字节序。
即使在 64 位架构中 intval(6123456789) = 6123456789,而 sprintf('%b', 5000000000) = 100101010000001011111001000000000
pack 不会将传递给它的任何内容视为 64 位。如果您想打包一个 64 位整数
<?php
$big = 5000000000;
$left = 0xffffffff00000000;
$right = 0x00000000ffffffff;
$l = ($big & $left) >>32;
$r = $big & $right;
$good = pack('NN', $l, $r);
$urlsafe = str_replace(array('+','/'), array('-','_'), base64_encode($good));
//done!
//rebuild:
$unurl = str_replace(array('-','_'), array('+','/'), $urlsafe);
$binary = base64_decode($unurl);
$set = unpack('N2', $tmp);
print_r($set);
$original = $set[1] << 32 | $set[2];
echo $original, "\\r\\n";
?>
结果是
数组
(
[1] => 1
[2] => 705032704
)
5000000000
但仅在支持 64 位的机器和 PHP 发行版上。
使用 pack 将阿拉伯字符写入文件。
<?php
$text = "㔆㘆㘆";
$text = mb_convert_encoding($text, "UCS-2BE", "HTML-ENTITIES");
$len = mb_strlen($text);
$bom = mb_convert_encoding("", "unicode", "HTML-ENTITIES");
$fp = fopen('text.txt', 'w');
fwrite($fp, pack('a2', $bom));
fwrite($fp, pack("a{$len}", $text));
fwrite($fp, pack('a2', $bom));
fwrite($fp, pack('a2', "\n"));
fclose($fp);
?>