Maison  >  Article  >  développement back-end  >  Introduction détaillée à la fonction pack et à la fonction unpack en PHP (avec code)

Introduction détaillée à la fonction pack et à la fonction unpack en PHP (avec code)

不言
不言avant
2019-02-25 09:58:113702parcourir

Cet article vous apporte une introduction détaillée à la fonction pack et à la fonction unpack en PHP (avec code). Les amis dans le besoin peuvent s'y référer.

PHP a deux fonctions impopulaires importantes : pack et unpack. Dans des scénarios tels que la programmation réseau et la lecture et l'écriture de fichiers image, ces deux fonctions sont presque essentielles. Compte tenu de l'importance de la lecture et de l'écriture de fichiers/de la programmation réseau, ou du traitement de flux d'octets, la maîtrise de ces deux fonctions constitue la base d'une programmation PHP avancée.

Cet article présente d'abord la différence entre 字节 et 字符, et explique la nécessité et l'importance de l'existence des deux fonctions. Ensuite, l'utilisation de base et les scénarios d'utilisation sont présentés pour donner aux lecteurs une compréhension générale et jeter les bases d'une utilisation réelle.

Octets et caractères

L'avantage de PHP est qu'il est simple et facile à utiliser. L'utilisation compétente des fonctions liées aux chaînes et aux tableaux peut répondre aux besoins généraux. Les chaînes sont souvent utilisées dans le travail quotidien, les développeurs PHP sont donc familiers avec les caractères, et ceux qui ont un peu d'expérience peuvent comprendre l'encodage des caractères. Mais de nombreux développeurs PHP ne connaissent pas le concept de caractères qui l'accompagne : les octets.

Ce n’est pas de leur faute. Le concept de "byte (stream)" apparaît rarement dans le monde PHP : il n'y a pas de mot-clé byte (et bien sûr pas de char), et la documentation officielle ne mentionne pas les bytes, il n'y a pas de support natif des tableaux (le tableau couramment utilisé est ; en fait une table de hachage ); bien sûr, les chaînes peuvent exprimer des tableaux d'octets (Byte Array, byte[]) dans d'autres langages.

Quelles sont les connexions et les différences entre les octets et les caractères ? En termes simples, les octets sont la plus petite unité de stockage et de fonctionnement de l'ordinateur, et les caractères sont les plus petites unités que les gens peuvent lire ; les octets sont des concepts de stockage (physiques), et les caractères sont des concepts logiques, les octets représentent des données (connotation et essence) et les caractères ; représentent leur signification ; les caractères sont composés d’octets.

Donnez quelques exemples pour illustrer la différence entre les deux : "Chine" contient 2 caractères, l'encodage GBK nécessite 4 octets et l'encodage UTF-8 nécessite 6 octets, le nombre "1234567890" contient 10 caractères sont représentés ; par le type int32 et ne nécessite que 4 octets ; l'image suivante occupe 42582 octets et représente "ma femme" en caractères, ce qui n'occupe que 3 caractères :

Introduction détaillée à la fonction pack et à la fonction unpack en PHP (avec code)

Donnons-en un autre exemple couramment utilisé pour illustrer la différence entre les caractères et les octets. En développement, nous utilisons souvent l'algorithme md5 pour obtenir la valeur de hachage des données. L'algorithme renvoie des données de 128 bits (16 octets). Afin de faciliter la visualisation de sa valeur, les utilisateurs utilisent classiquement une représentation hexadécimale, et le résultat est une chaîne de 32 bits bien connue (non sensible à la casse). La chaîne de 32 octets n'est pas le résultat inévitable de l'algorithme md5, les données de 16 octets sont son essence. Si vous le souhaitez, vous pouvez utiliser un nombre inférieur à 2 ^ 128 pour représenter le résultat du hachage, ou vous pouvez encoder en base64 les 16 octets comme résultat. Par conséquent, la relation entre la valeur de hachage 32 bits couramment utilisée et les 16 octets renvoyés par md5 est la suivante : l'une est la représentation des caractères et l'autre est son essence (tableau de caractères) (la deuxième valeur du paramètre de la fonction md5 de PHP est fidèle à obtenir des données de section de 16 caractères, ou le troisième paramètre de la fonction de hachage est vrai).

Les concepts associés incluent l'ordre des octets, le codage des caractères, etc., qui ne seront pas développés dans cet article.

Introduction

Il existe des dizaines de fonctions en PHP spécialisées dans le traitement des chaînes. En ajoutant des fonctions régulières, temporelles et autres, il n'y a pas moins d'une centaine de fonctions pour les chaînes. traitement. En revanche, le traitement des octets n'est pas populaire et il n'existe que quelques fonctions associées. En plus des ord/chr couramment utilisés, des octets d'origine renvoyés par la fonction de chiffrement de hachage, et des openssl_random_pseudo_bytes et autres fonctions de la bibliothèque openssl traitent ou renvoient réellement octets, les deux fonctions de traitement d'octets les plus importantes sont pack et unpack.

Cette section mène à l'utilisation de la fonction pack à partir du problème.

Question

Considérons une question simple : Comment la réponse ultime à l'univers 42 est-elle représentée en mémoire (ou comment obtenir son tableau d'octets) ?

Comme 42 est un entier, sa taille en octets peut être de 1, 2, 4, 8, etc. selon le matériel. Ici, nous limitons un entier à occuper 4 octets, donc la formulation équivalente du problème est : Comment convertir un entier en tableau d'octets (ordre natif, 4 octets) ?

Analyse

Parce qu'elle est multi-octets, la question de l'ordre des octets doit être prise en compte. 42 ne dépasse pas 255 et n'occupe qu'un seul octet, donc les trois autres octets sont tous à 0. Sur cette base, nous pouvons tirer la conclusion : s'il est big-endian (l'octet de poids faible est stocké dans l'adresse de poids fort), les quatre octets sont : 0 0 0 42 si c'est le cas. est petit-boutiste, le résultat est : 42 0 0 0.

Alors comment connaître l'ordre des octets de la machine ? PHP ne fournit pas de fonctions associées, et ne peut pas non plus accéder directement à l'adresse et accéder aux données d'octets comme la langue C. Comment le tout-puissant PHP peut-il corriger l’ordre des octets ou terminer la conversion des données en octets ?

Solution

Au niveau de l'application PHP, la conversion des données en octets (tableaux) est un événement spécial pour pack, et la conversion des octets (tableaux) en données est un événement spécial événement pour unpack. Hormis ces deux fonctions, il est quasiment impossible de convertir un tableau d'octets (ou des données binaires) en données (merci de me donner quelques conseils si possible).

现在我们用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函数的元字符主要分为三类:

  1. 字符串:aA等;将数据转成字符串,功能上与sprintf类似,例如整数32转换成字符串"32";

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

  3. 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
)

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

Caution If 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数据库文件等场合,packunpack几乎必不可少。本文举例说一下packunpack在网络编程时协议解析的用途。

假设我们的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。

总结

En plus de l'allocation de mémoire, les appels système les plus importants sont la lecture et l'écriture de fichiers et la connexion réseau, et les objets d'opération essentiels des deux sont les flux d'octets. pack et unpack offrent à PHP la possibilité d'effectuer des opérations sur les octets de bas niveau, ce qui est très utile dans le traitement des données binaires. Les développeurs PHP qui souhaitent sortir de la programmation Web doivent maîtriser ces deux fonctions.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer