php pack && unpack函数详解

在看下面一个例子前先提及一组函数pack,unpack。
任何一款拥有socket操作能力的语言都有一个专门用于组包的函数,php也不例外当然这组函数的用途不仅仅是组包。
下面简单的介绍一下:
应用一:
输入16进制或者2进制流。
<?php
$src="3B06";
$binvar = pack('H*',$src);
echo $binvar;
?>

看看这个程序,相当于下面的程序
echo chr(0x3B).chr(0x06);

在数据量很小的时候后面的做法,更为简便。但是大量数据的时候,前一种做法则更为实际工整些,代码量也很少。

例:
@pairs=split(/&/,$query);
foreach $pairs (@pairs) {
($name,$value)=split(/=/,$pairs);
$value=~tr/+/ /;
$value=~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
$FORM{$name}=$value;
}

解释:
pack TEMPLATE, LIST

这个函数接收一个普通 Perl 数值的 LIST 并且根据 TEMPLATE 把它们转换成一个字节串并且返回该字串。参数列表在必要时会被填充或者截除。也就是说,如果你提供的参数比 TEMPLATE 要求的少,pack 假设缺的都是空值。如果你提供的参数比 TEMPLATE 要求的多,那么多余的参数被忽略。在 TEMPLATE 里无法识别的格式元素将会抛出一个例外。

这个模板把该字串的结构描述成一个域序列。每个域都由一个字符代表,描述该值的类型和其编码。比如,一个格式字符 N 声明一个四个字节的无符号整数,字节序是大头在前。

域是以模板中给出的顺序包装的。比如,如果要把一个一字节的无符号整数和一个单精度浮点数值包装到一个字串里,你要说:
   $string = pack("CF", 244, 3.14);

返回的字串的第一个字节的值是 244。剩下的字节是 3.14 作为单精度浮点数的编码。浮点数的具体编码方式取决于你的计算机的硬件。

有些包装时要考虑的重要事情是:
数据的类型(比如是整数还是浮点还是字串),
数值的范围(比如你的整数是否放在一个,两个,四个,或者甚至八个字节里;或者你包装的是一个 8 位字符还是 Unicode 字符。),
你的整数是有符号还是无符号,以及
使用的编码(比如说本机,包装位和字节时小头在前,或者是大头在前)。

表 29-3 列出了格式字符以及它们的含义。(其他字符也可能在格式中出现;它们在稍后介绍。)

表29-3,pack/unpack 的摸板字符字符 含义
a 一个填充空的字节串
A 一个填充空格的字节串
b 一个位串,在每个字节里位的顺序都是升序
B 一个位串,在每个字节里位的顺序都是降序
c 一个有符号 char(8位整数)值
C 一个无符号 char(8位整数)值;关于 Unicode 参阅 U
d 本机格式的双精度浮点数
f 本机格式的单精度浮点数
h 一个十六进制串,低四位在前
H 一个十六进制串,高四位在前
i 一个有符号整数值,本机格式
I 一个无符号整数值,本机格式
l 一个有符号长整形,总是 32 位
L 一个无符号长整形,总是 32 位
n 一个 16位短整形,“网络”字节序(大头在前)
N 一个 32 位短整形,“网络”字节序(大头在前)
p 一个指向空结尾的字串的指针
P 一个指向定长字串的指针
q 一个有符号四倍(64位整数)值
Q 一个无符号四倍(64位整数)值
s 一个有符号短整数值,总是 16 位
S 一个无符号短整数值,总是 16 位
u 一个无编码的字串
U 一个 Unicode 字符数字
v 一个“VAX”字节序(小头在前)的 16 位短整数
V 一个“VAX”字节序(小头在前)的 32 位短整数
w 一个 BER 压缩的整数
x 一个空字节(向前忽略一个字节)
X 备份一个字节
Z 一个空结束的(和空填充的)字节串
@ 用空字节填充绝对位置


你可以在你的 TEMPLATE 里自由地使用空白和注释。注释以惯用的 # 符号开头并且延伸到 TEMPLATE 里第一个换行符(如果存在)。

每个字母后面都可以跟着一个数字,表示 count(计数),解释成某种形式的重复计数或者长度,具体情况取决于格式。除了a,A,b,B,h,H,P,和 Z 之外,所有格式的 count 都是重复次数,因此 pack 从 LIST 里吃进那么多数值。如果 count 是一个 * 表示剩下的所有东西。

a,A 和 Z 格式只吃进一个数值,但是把它当作一个长度为 count 的字节串打包,并根据需要填充空或者空格。在解包的时候,A 抽去结尾的空格和空,Z 抽去第一个空后面的所有东西,而 a 原封不动地返回文本数据。打包的时候,a 和 Z 是相同的。

与之类似,b 和 B 格式打包一个长度为 count 的位串。输入域里的每个字节都以每个输入字节的最低位(也就是 ord($byte) % 2)为基础生成结果中的 1 个位。方便一点来说,这意味着字节 0 和 1 生成位 0 和 1。从输入字串的开头开始,每 8 个字节转换成一个字节的输出。如果输入字串的长度不能被 8 整除,那么余下的部分用 0 补齐。类似,在 uppack 的时候,任何额外的位都被忽略。如果输入字串比需要的长,那么多出来的部分被忽略。count 如果是 * 意思是使用输入域的所有字节。在解包的时候,这些位转换成一个 0 和 1 组成的字串。

h 和 H 格式打包一个由 count 个半字节(4 位组,常用于代表十六进制位。)组成的字串。

p 格式打包一个指向一个空结尾的字串。你有责任确保该字串不是一个临时数值(因为临时值可能在你能使用打包的结果之前很可能被释放掉)。P 格式打包一个指向一个结构的指针,该结构的大小由 count 指明。如果对应的 p 或 P 的值是 undef,则创建一个空指针。

/ 字符允许对这样一个字串进行打包或解包:这个打了包的结构包含一个字节数以及后面跟着字串本身。你可以这样写 length-item/string-item。length-item 可以是任意 pack 模板字符,并且描述长度值是如何打包的。最常用的可能是那些整数打包的东西,比如 n(用于打包 Java 字串),w(用于打包 ASN.1 或 SNMP)以及 N(用于 Sun XDR)。string-item 目前必须是 A*,a*,或者 Z*。对于 unpack 而言,字串的长度是从 length-item 里获取的,但是如果你放 * 进去,那么它将被忽略。
   unpack 'C/a',   "\04Gurusamy";      # 生成 'Guru'
uppack 'a3/A* A*', '077 Bond J ';      # 生成 (' Bond', 'J')
pack 'n/a* w/a*', 'hell', ','world';      #  生成 "hello, world"

length-item 不会从 unpack 明确地返回。向 length-item 字母加一个 count 不一定能干有用的事,除非该字母是 A,a,或 Z。用带 length-item 的 a 或 Z 进行打包可能会引入空(\0)字符,这时候,Perl 在会认为它不是合法数字字串。

整数格式 s,S,l,和 L 的后面可以紧跟一个 !,表示本机的短整数或者长整数,而不是各自准确的 16 位和 32 位。现在,这是一个在许多 64 位平台上的问题,在这些平台上本地 C 编译器看到本机短整数和长整数可能和上面的这些值不同。(i! 和 I! 也可以用,不过只不过是为了保持完整;它们与 i 和 I 相同。)

你可以通过 Config 模块获取制作你的 Perl 的平台上的本机的 short,int,long 和 long long 的实际长度:
   use Config;
print $Config{shortsize},      "\n";
print $Config{intsize},      "\n";
print $Config{longsize},      "\n";
print $Config{longlongsize},   "\n";

这里只是说 Configure 知道一个 long long 的大小,但着并不意味着你就能用 q 和 Q。(有些系统能有,不过你用的系统很可能还没有。)

长度大于一个字节的整数格式(s,S,i,I,l,和 L)都是天生不能在不同处理器之间移植的,因为它们要遵从本机字节序和位权重序的规则。如果你需要可移植的整数,那么使用格式 n,N, v,和 V;因为它们是字节权重序并且是尺寸已知的。

浮点数只以本机格式存在。因为浮点格式的千差万别而且缺乏一种标准的“网络”表现形式,所以没有做任何浮点数交换的工具。这意味着在一台机器上打包了的浮 点数数据可能不能在另外一台上读。甚至如果两台机器都使用 IEEE 浮点数算法的话,这都仍然是一个问题,因为与权重相关的内存表现形式不是 IEEE 规范的一部分。

Perl 在内部使用双精度数进行所有浮点数计算,所以从 double 转换成 float,然后又转换成 float 会损失精度。这就意味着 unpack("f", pack("f", $foo)) 可能不等于 $foo。

你有责任为其他程序考虑任何对齐或填充的问题,尤其是那些带着 C 编译器自己的异质概念的 C struct 的程序,C 编译器在不同的体系结构上对 C struct 如何布局有着巨大的差别。你可能需要在打包时增加足够的 x 来弥补这个问题。比如,一个 C 声明:
   struct foo {
unsigned char c;
float f;
};

可以写成一个“C x f”格式,一个“C x3 f”格式,或者甚至是“f C”格式——而且这只是其中一部分。pack 和 unpack 函数把它们的输入和输出当作平面的字节序列处理,因为它们不知道这些字节要去哪儿,或者从哪儿来。

让我们看一些例子,下面的第一部分把数字值包装成字节:
   $out = pack "CCCC", 65, 66, 67, 68;      # $out 等于"ABCD"
$out = pack "C4", 65, 66, 67, 68;         # 一样的东西

下面的对 Unicode 的循环字母做同样的事情:
   $foo = pack("U4", 0x24b6, 0x24b7, 0x24b8, 0x24b9);

下面的做类似的事情,增加了一些空:
   $out = pack "CCxxCC", 65, 66, 67, 68;      # $out 等于 "AB\0\0CD"

打包你的短整数并不意味着你就可移植了:
   $out = pack "s2", 1, 2;         # 在小头在前的机器上是 "\1\0\2\0"
# 在大头在前的机器上是 "\0\1\0\2"

在二进制和十六进制包装上,count 指的是位或者半字节的数量,而不是生成的字节数量:
   $out = pack "B32", "...(略)";
$out = pack "H8", "5065726c";         # 都生成“Perl”

a 域里的长度只应用于一个字串:
   $out = pack "a4", "abcd", "x", "y", "z";      # "abcd"

要绕开这个限制,使用多倍声明:
   $out = pack "aaaa",    "abcd", "x", "y", "z";   # "axyz"
$out = pack "a" x 4,   "abcd", "x", "y", "z";   # "axyz"

a 格式做空填充:
   $out = pack "a14", "abcdefg";         # " abcdefg\0\0\0\0\0\0"

这个模板打包一个 C 的 struct tm 记录(至少在一些系统上如此):
   $out = pack "i9pl", gmtime(), $tz, $toff;

通常,同样的模板也可以在 unpack 函数里使用,尽管一些模板的动作不太一样,特别是 a,A,和 Z。

如果你想把定长文本域连接到一起,可以用 TEMPLATE 是多个 a 或者 A 的 pack:
$string = pack("A10" x 10, @data);

如果你想用一个分隔符连接变长文本域,那么可以用 join 函数:
   $string = join(" and ", @data);
$string = join ("", @data);         #  空分隔符

尽管我们所有的例子都使用文本字串做模板,但我们没有理由不让你使用来自磁盘文件的模板。你可以基于这个函数做一个完整的关系型数据库。(我们不会想知道那样能证明你什么东西。)
posted @ 2014-10-24 13:54  lemon66  阅读(622)  评论(0编辑  收藏  举报