原标题:PHP中的pack和unpack函数

PHP有两个重要的冷门函数: pack 和 unpack 。在网络编程,读写图像文件等场景,这两个函数几乎必不可少。鉴于文件读写/网络编程,或者说字节流处理的重要性,掌握这两个函数是迈向高级PHP编程的基础。

本文先介绍 字节 和 字符 的区别,说明两个函数存在的必要性和重要性。然后介绍基本用法和使用场景,让读者对其有大体了解,为实际使用中奠定基础。

字节和字符

PHP的优势是简单易用,熟练运用 字符串和 数组相关函数就能抗住一般的需求。日常工作中多用到字符串,所以PHP开发对字符都比较熟悉,稍微资深点基本能也能弄清字符编码。但字符的伴生概念:字节,不少PHP开发并不知晓/熟悉。

这不怪他们。PHP世界里极少出现“字节(流)”的概念:没有 byte 关键字(当然也没有 char),官方文档也没提字节;没有原生的数组支持(常用的 array 其实是 hashtable );当然字符串(string)能表达其他语言中的字节数组(Byte Array, byte[] )。

字节和 字符有什么联系和区别呢?简单来说 字节是计算机存储和操作的最小单位,字符是人们阅读的最小单位 ;字节是 存储(物理)概念,字符是 逻辑概念;字节代表 数据(内涵和本质),字符代表其 含义; 字符由字节组成。

举几个例子说明两者区别:“中国”包含2个字符,GBK编码表示需要4个字节,UTF-8编码需要6个字节;数字“1234567890”,包含10个字符,用 int32 类型表示只需4个字节;下面的图片占用42582个字节,用字符表示是“我老婆”,只占用3个字符:

再举一个常用的例子说明字符和字节的区别。开发中我们常用 md5 算法获取数据的哈希值,算法返回一个128位(bit)的数据(16个字节)。为方便查看其值,人们约定成俗地用十六进制表示,结果就是我们熟知的32位长度的字符串(不区分大小写)。32长度字符串不是 md5 算法的必然结果, 16字节数据才是其本质。如果你愿意,可以用一个小于 2^128 的数字表示哈希结果,也可以将16字节 base64 编码后作为其结果。所以常用的32位哈希值与 md5 返回的16字节关系为:一个是字符表示,另一个则是其本质(字符数组)(PHP的 md5 函数第二个参数值为 true 便可得到16字节数据,或 hash 函数第三个参数为 true )。

相关概念还有 字节序、 字符编码等,本文不做展开。感兴趣的读者可参考本人之前的博客“文件和字符编码”或相关材料。

引言

PHP中专门处理字符串的函数有几十个,加上正则、时间等函数,字符串处理的函数不下百个。相比之下字节处理门庭冷落,相关函数寥寥无几。除了常用的 ord/chr ,哈希加密函数返回的原始字节、openssl库的 openssl_random_pseudo_bytes 等函数 真正处理或返回字节外,最重要的两个字节处理函数是 pack 和 unpack 。

本节从问题引出 pack 函数的使用。

问题

考虑一个简单的问题:宇宙的终极答案 42在内存中是如何表示的(或者说怎么获取其字节数组)?

因为42是一个整数,根据硬件不同,其占用字节大小可能为1, 2, 4, 8等。这里我们限定一个整数占用4个字节,于是问题的等价表述为:怎样将一个整数转换成字节数组(本机序,4个字节)?

分析

因为是多字节,所以要考虑字节序的问题。42不超过255,只占用一个字节,故而其他三个字节都是0。据此得到结论:如果是大端序(低位字节存放在地址高位),四个字节分别是: 0 0 0 42;如果是小端序,结果则是: 42 0 0 0。

那怎么知道机器的字节序呢?PHP没有提供相关功能,也不能像 C 语言直接取地址访问字节数据。无所不能的PHP该怎么搞定字节序,或者说完成数据向字节的转换?

方案

PHP应用层面,数据向字节(数组)的转换是 pack 的专场,字节(数组)向数据的转换则是 unpack 的专场。除这两个函数,字节数组(或二进制数据)向数据的转换几无可能(如果有请不吝指教)。

现在我们用 pack 函数获取42在内存中的字节数组。相关代码如下:

function intToBytes(int $num) : string {

return pack("l", $num);

}

function outputBytes(string $bytes) {

echo "bytes: ";

for ($i = 0; $i < strlen($bytes); ++ $i) {

echo ord($bytes[$i]), " ";

}

echo PHP_EOL;

}

outputBytes(intToBytes(42));

// 程序输出:

bytes: 42 0 0 0

本人计算机用的英特尔的CPU,x86架构是小端序,所以程序输出符合预期。

延伸一下,怎么判断机器的字节序?有了 pack 函数,答案非常简单:

function bigEndian() : bool {

$data = 0x1200;

$bytes = pack("s", $data);

return ord($bytes[0]) === 0x12;

}

调用函数便返回本机是否大端序。

上述是 pack 函数简单的使用场景,接下来分别介绍 pack 和 unpack 函数。

pack 和 unpack pack 函数

pack 是“打包/封包”的意思。如其名, pack 函数的工作是将数据按照格式打包成字节数组。函数原型为:

pack ( string $format [, mixed $… ] ) : string

形式上与 printf 系列函数相同:第一个参数是格式字符串,其余参数是要格式化的参数。不同之处在于 pack 函数的格式中 不能出现元字符和量词外的其他字符,所以不需要 % 符号。

上文的例子中使用了”l”和”s”两个格式化元字符, pack 函数的元字符主要分为三类:

字符串: a 、 A 等;将数据转成字符串,功能上与 sprintf 类似,例如整数32转换成字符串”32″;

字节: h 和 H ;对字节进行16进制编码,区别在于低位还是高位在前,功能上与 dechex 等函数类似;

char/short/int/long/float/double六种基本类型: c/s/i/l 等;将数据转换成对应类型的字节数组,除 char 类型外(暂)没有其他函数可替代;

注意: char 和 a/A 等的区别是 a/A 等输入为字符(串),而’s/S’的输入要求是小于256的 整数,输入 字符会得到0。

量词比较简单:数字和” “两种。例如”i2″表示将两个参数按照整数转换,”c “表示后续都按照 char 类型转换。

unpack

unpack 是 pack 的反向操作:将字节数组解析成有意义的数据。其函数原型为:

unpack ( string $format , string $data [, int $offset = 0 ] ) : array

unpack 函数需要注意的是第一个参数和返回值。返回值好理解, pack 函数相当于将除格式化参数外的参数数组(想象成 call_user_func_array 的参数)变成一个字节数组; unpack 做相反的事情:释放数据,得到输入时的参数数组。

返回一个数组,其键分别是什么呢?这便是格式化参数( $format )在 pack 和 unpack 的不同之处: unpack 应该对释放出来的数据命名,用”/”分隔各组数据。由于格式化参数允许有非元字符和量词外的字符,为了区分数据,不同数据间的”/”分隔符必不可少。

一个例子:

$bytes = pack("iaa*", 42, ":", "The answer to life, the universe and everything");

outputBytes($bytes);

$result = unpack("inumber/acolon/a*word", $bytes);

print_r($result);

// 程序输出:

bytes: 42 0 0 0 58 84 104 101 32 97 110 115 119 101 114 32 116 111 32 108 105 102 101 44 32 116 104 101 32 117 110 105 118 101 114 115 101 32 97 110 100 32 101 118 101 114 121 116 104 105 110 103

Array

(

[num] => 42

[colon] => :

[word] => The answer to life, the universe and everything

)

如果不对释放出来的数据命名会怎么样?例如上例中 unpack 的格式化参数为: “i/a/a*”,结果是什么呢?其结果为:

Array

(

[1] => The answer to life, the universe and everything

)

为何?官方文档上如是说:

CautionIf you do not name an element, numeric indices starting from 1 are used. Be aware that if you have more than one unnamed element, some data is overwritten because the numbering restarts from 1 for each element.

翻译过来就是:如果你不对数据命名,默认的1, 2, 3…就用来当作键值。如果有多组数据,每组都用同样的下标,会导致数据覆盖。

所以能理解 “i/a/a*”为何只剩最后一组数据了吧?

应用场景

读取图像、word/excel文件,解析binlog、二进制ip数据库文件等场合, pack 和 unpack 几乎必不可少。本文举例说一下 pack 和 unpack 在网络编程时 协议解析的用途。

假设我们的tcp包格式为:前四个字节表示包大小,其余字节为数据内容。于是客户(发送)端的send 函数可以长这样:

public function send($data) {

// 这里假设$data已经做了序列化、加密等操作,是字节数组

// 计算报文长度,封装报文

$len = strlen($data);

$header = pack("L", $len);

// 转换成网络(大端)序

$header = xxx

// 封包

$binary = $header . $data;

// 调用fwrite/socket_send等将数据写入内核缓冲区

...

}

服务(接收)端根据协议解析接收到的数据流:

public function decodable($session, $buffer) {

$dataLen = strlen($buffer);

// 非法数据包

if ($dataLen < 4) {

// 关闭连接、记录ip等

....

return NOT_OK;

}

// 获取前四个字节

$header = substr($buffer, 0, 4);

// 转换成主机序

$header = xxx

// 解析数据长度

$len = unpack("L", $header);

// 单个报文不能超过8M,例如限制上传的图像大小

if ($len > 8 * 1024 * 1024) {

// 关闭连接等

return NOT_OK;

}

// 检查数据包是否满足协议要求

if ($dataLen - 4 >= $len) {

return OK;

}

// 数据未全部到达,继续等待

return NEED_DATA;

}

通过 pack 和 unpack ,我们顺利的处理报文协议和二进制字节流的发送和解析。

如果你用 n 作为报文分隔符, pack 和 unpack 也许用不到。但在网络通讯中直接传递字符毕竟少数(相当于明文传送),大多数情况下的二进制数据流的解析还是要靠 pack 和 unpack。

总结

除分配内存,最重要的系统调用莫过于文件读写和网络连接,而两者的本质操作对象都是字节流。 pack 和 unpack 为PHP提供了底层字节操作的能力,在二进制数据处理中十分有用。有志于跳出web编程的PHP开发应该都要掌握这两个函数。

文件和字符编码

PHP Manual: pack

PHP Manual: unpack

Handling binary data in PHP with pack() and unpack()

PHP: 深入pack/unpack

责任编辑:

php unpack,PHP中的pack和unpack函数相关推荐

  1. PHP中的pack和unpack函数

    转载请注明文章出处:https://tlanyan.me/php-pack-a... PHP有两个重要的冷门函数:pack和unpack.在网络编程,读写图像文件等场景,这两个函数几乎必不可少.鉴于文 ...

  2. 解释一下pack和unpack

    pack/unpack runbase Framwork用pack和unpack来保存用户上次操作的值,用户上次操作的值会存在SysLastValue表中,这个功能确实蛮酷,不过今天差点被这个东西给整 ...

  3. socket中pack 和 unpack 的使用

    任何一款拥有socket操作能力的语言都有一个专门用于组包的函数,php也不例外! 用了很久php了却很少有机会用php进行一些二进制操作. 最近用php写一个socket客户端连接一个用C++语言开 ...

  4. PHP: chr和pack、unpack那些事

    为什么80%的码农都做不了架构师?>>>    PHP是一门很灵活的语言.正因为它太灵活了,甚至有些怪异,所以大家对它的评价褒贬不一.其实我想说的是,任何一门语言都有它自身的哲学,有 ...

  5. pack 和 unpack

    摘要 PHP作为一门为web而生的服务器端开发语言,被越来越多的公司所采用.其中不乏大公司,如腾迅.盛大.淘米.新浪等.在对性能要求比较高的项目中,PHP也逐渐演变成一门前端语言,用于访问后端接口.或 ...

  6. python的pack和unpack用法

    python的pack和unpack用法 pack和unpack在处理二进制流中比较常用的封包.解包格式 按照给定的格式(fmt),把数据封装成字符串(实际上是类似于c结构体的字节流) pack(fm ...

  7. WPF中的Pack URI

    问题 说来也简单:首先,我在WPF项目中建立了一个用户自定义控件(CustomControl),VS模板为我们自动生成了 CustomControl1和Theme文件夹(里边包含一个Generic.x ...

  8. Unix/Linux中的read和write函数

    文件描述符 对于内核而言,所有打开的文件都通过文件描述符引用.文件描述符是一个非负整数.当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符.当读或写一个文件时,使用open或creat ...

  9. 如何在sqlite3连接中创建并调用自定义函数

    #!/user/bin/env python # @Time :2018/6/8 14:44 # @Author :PGIDYSQ #@File :CreateFunTest.py '''如何在sql ...

  10. WinCE中串口驱动及接口函数介绍(转载)

    作者:ARM-WinCE 在WinCE中,串口驱动实际上就是一个流设备驱动,具体架构如图: 串口驱动本身分为MDD层和PDD层.MDD层对上层的Device Manager提供了标准的流设备驱动接口( ...

最新文章

  1. JS ES6 实用笔记
  2. 【完整代码】Scala AKKA实现两个Actor之间的通信代码示例
  3. Android ListView几个重要属性
  4. 二次扩增产物条带弥散_PCR实验操作常见解决方法
  5. 容器编排技术 -- 使用Minikube集群
  6. CPU的内部物理结构介绍
  7. Centos7常用命令[网络]
  8. MySQL数据库无法启动的简单排错
  9. 编译nginx源码包
  10. ukey其他错误_关于税务UKey使用常见问题解答
  11. SAP各模块表清单及逻辑关系介绍
  12. Netlog的数据库及LAMP架构
  13. 猎豹傅盛:升维思考,降维攻击!(深度好文)
  14. 百度地图的反地址解析(通过经纬度查询地址信息)
  15. (译)对词向量化的直观理解:从计数向量到Word2Vec
  16. linux mysql 开发环境_RedHat Linux下QT平台MySQL数据库开发环境配置
  17. HPC高性能计算知识: HPDA新兴技术分析(附下载)
  18. 集线器、路由器、交换机
  19. antvf2动态数据_浅谈ES6基础——Promise
  20. 祝全天下老师教师节快乐

热门文章

  1. [日常] 修改编辑word中的页眉页脚
  2. c#winform——Gobang五子棋简易版双人对战制作(基本结构+代码)
  3. 放慢脚步是为了走得更快
  4. php中间件最简单实现,my_test_simple_framework
  5. psd转html的素材,Ai2Psd:一键ai转psd格式脚本
  6. 北航计算机学院好气派,2017年北京航空航天大学国内排名第几
  7. 如何以椭圆形显示位图
  8. python爬取链家二手房信息并存储到数据库
  9. linux 查看内存fru,linux – 查找NIC的网络百分比
  10. 价值几百元的EMlog仿大表哥资源网模版