最近从网上获取到一份IP地理位置文件库,但其采用二进制格式保存,且其中的解析语言为PHP,由此想到不如借此机会总结下PHP的二进制处理。
PHP作为Web开发的上层语言,使用二进制的场景不是很多,故在平时开发中很少接触。但正因如此,一旦接触,多数PHP开发人员多是一脸懵逼。关于其使用场景本人不敢多妄言,涉及过的有数据存储,使用PHP解析二进制存储的文件库,如引言部分的IP地理位置库
服务调用,服务间调用采用二进制协议,如语言无关的RPC通信框架thrift
二进制多使用在较为底层的服务,传输高效,解析速度快。缺点是不便于调试与扩展。
本文是关于PHP的二进制处理,但在此之前先简单介绍一些较为基础的计算机知识。
二进制
二进制是采用0和1两个符号表示数值的一种形式,个人认为其可理解为计算机的母语,是计算机发展的基础。下面列举些二进制与十进制的数值对应 01=>1 10=>2 11=>3
二进制的最小单位,即是计算机的最小单位,为比特bit。由8个bit组成的单位是1字节byte。除了比特bit与字节byte外,在计算机中还有一个字长单位,视硬件与操作系统而定,当然主要是由CPU决定。在之前很长一段时间我们使用的计算机字长都是32bit,即4个字节,现在多为64bit,即8个字节。
字节顺序
为什么要谈字节顺序?
首先并非所有单位存储对象都为一个字节可表示,如short类型需2个字节,int类型需4个字节等。那此处便会引出一个问题,多字节存储对象字节顺序是如何呢?假设有一个int类型数据0x12345678。
大端模式
存储字节顺序可能如下addr0x00000x00040x00080x0012data0x120x340x560x78
此时存储方式为高位低地址,高位数在低位地址。即我们常说的大端模式,符合我们的阅读习惯。
小端模式
当然也可能相反顺序,即可能如下addr0x00000x00040x00080x0012data0x780x560x340x12
此时存储方式为高位高地址,高位数在高位地址。即我们常说的小端模式
网络字节序
无论大端小端,对于我们都不是那么重要。只要规则定好,顺序并非那么重要。当然这仅限单机,当多台计算机间网络通信时,我们就需关注这种差异。
因我们并不知道互相通信计算机间大小端模式,所以规定在网络上传输的数据统一为大端模式,又称为网络字节序。此种方式可防止不同计算机间的差异。
PHP的pack与unpack
关于PHP的二进制处理,当然就要提到pack和unpack函数,这是PHP中用来打包和解包二进制的两个函数。
函数介绍// 二进制打包函数
string pack(string $format [, mixed $args [, mixed $... ]]) // 二进制解包函数
string unpack(string $format, string $args)
参数$format是我们重点需要理解的参数,下面逐一介绍各种格式
a 以null字符填充空白字符,即0x00 A 以空格字符填充空白字符,即0x20
示例如下<?php $a = pack("a4", "tt");$b = pack("A4", "ss");echo bin2hex($a) . PHP_EOL;echo bin2hex($b) . PHP_EOL;print_r(unpack("a4str", $a));print_r(unpack("A4str", $b));
返回结果如下:6100000062202020Array( [str] => a)Array( [str] => b)
首先a和b分别以a4和A4格式,其中4表示数据占据4个字节,结果61为a,62为b,此处打包数据不足4个字节,a与A分别以null字符和space字符填充。unpack采用同样规则,便可对二进制数据解包。h 十进制数作为十六进制字符串,如10即为0x10,低位在前以半字节为单位H 十进制数作为十六进制字符串,如10即为0x10,高位在前以半字节为单位
这个规则有点拗口,直接看示例解释结果<?php $a = pack("h2", 0x51);$b = pack("H2", 0x51);echo bin2hex($a) . PHP_EOL;echo bin2hex($b) . PHP_EOL;
输出结果为1881
h与H这两个规则究竟是如何的呢?首先我们将0x51转化为十进制数据,即为81.h为低位在前即为18,H为高位在前即为81。同样可采用unpack实现解包,这里不再赘述。
继续往下看其他格式类型s 有符号短整型(16位, 主机字节序)S 无符号短整型(16位, 主机字节序)n 有符号短整型(16位, 大端字节序)v 无符号短整型(16位, 小端字节序)
我们可以利用上面的format,判断本地的大小端<?php $a = pack("s", 0x5223) . PHP_EOL;$b = pack("n", 0x5223) . PHP_EOL;$c = pack("v", 0x5223) . PHP_EOL;if ($a === $b){ echo "小端" . PHP_EOL;}elseif ($a === $c){ echo "大端" . PHP_EOL;}
本人电脑输出为“大端”,由此可以判断计算机的大小端。当然这不是什么好方法。
还有更多使用格式规则,就不一一介绍了,有兴趣的朋友可多多实验尝试c 转为有符号charC 转为无符号chari 有符号整数(依赖机器大小及字节序)I 无符号整数(依赖机器大小及字节序)l 有符号整型(32位, 主机字节序)L 无符号整数(32位, 主机字节序)N 无符号长整型(32位, 大端字节序)V 无符号长整型(32位, 小端字节序)q 有符号长长整型(64位, 主机字节序)Q 无符号长长整型(64位, 主机字节序)J 有符号长长整型(64位,大端字节序)P 有符号长长整型(64位,小端字节序)f 单精度浮点型 (依赖机器大小范围)d 双精度浮点型 (依赖机器大小范围)
总的来说,PHP的二进制处理函数,就是将我们平时常用的功能进行封装,提高二进制处理的工作效率。而不用像在C语言中,使用memcpy,memset,strlen或者<>右移之类的操作实现。