Heim > Artikel > Backend-Entwicklung > Detaillierte Einführung in die Pack- und Entpack-Funktion in PHP (mit Code)
Dieser Artikel bietet Ihnen eine detaillierte Einführung in die Pack- und Entpackfunktion (mit Code). Ich hoffe, dass er für Sie hilfreich ist.
PHP hat zwei wichtige unpopuläre Funktionen: pack
und unpack
. In Szenarien wie der Netzwerkprogrammierung sowie dem Lesen und Schreiben von Bilddateien sind diese beiden Funktionen nahezu unerlässlich. Angesichts der Bedeutung des Lesens und Schreibens von Dateien/Netzwerkprogrammierung oder der Bytestream-Verarbeitung ist die Beherrschung dieser beiden Funktionen die Grundlage für fortgeschrittene PHP-Programmierung.
In diesem Artikel wird zunächst der Unterschied zwischen 字节
und 字符
vorgestellt und die Notwendigkeit und Bedeutung der Existenz der beiden Funktionen erläutert. Anschließend werden die grundlegenden Nutzungs- und Nutzungsszenarien vorgestellt, um den Lesern ein allgemeines Verständnis zu vermitteln und den Grundstein für die tatsächliche Nutzung zu legen.
Bytes und Zeichen
Der Vorteil von PHP besteht darin, dass es einfach und benutzerfreundlich ist und allgemeine Anforderungen erfüllen kann. Da in der täglichen Arbeit häufig Zeichenfolgen verwendet werden, sind PHP-Entwickler mit Zeichen vertraut und diejenigen mit ein wenig Erfahrung können die Zeichenkodierung im Grunde verstehen. Aber viele PHP-Entwickler sind sich des zugehörigen Zeichenkonzepts nicht bewusst/vertraut: Bytes.
Es ist nicht ihre Schuld. Das Konzept von „Byte (Stream)“ kommt in der PHP-Welt selten vor: Es gibt kein Byte-Schlüsselwort (und natürlich auch kein char), und in der offiziellen Dokumentation werden Bytes nicht erwähnt. Es gibt keine native Array-Unterstützung (das häufig verwendete Array). eigentlich hashtable); natürlich können Strings Byte-Arrays (Byte Array, byte[]) in anderen Sprachen ausdrücken.
Was sind die Zusammenhänge und Unterschiede zwischen Bytes und Zeichen? Einfach ausgedrückt sind Bytes die kleinste Einheit der Computerspeicherung und -operation, und Zeichen sind die kleinsten Einheiten, die Menschen lesen können stellen ihre Bedeutung dar. ;Zeichen bestehen aus Bytes.
Nennen Sie einige Beispiele, um den Unterschied zwischen den beiden zu veranschaulichen: „China“ enthält 2 Zeichen, die GBK-Kodierung erfordert 4 Bytes und die UTF-8-Kodierung erfordert 6 Bytes. Die Zahl „1234567890“ enthält 10 Zeichen vom Typ int32 und benötigt nur 4 Bytes; das folgende Bild belegt 42582 Bytes und stellt „meine Frau“ in Zeichen dar, die nur 3 Zeichen einnehmen:
Geben wir noch eins häufig verwendetes Beispiel zur Veranschaulichung des Unterschieds zwischen Zeichen und Bytes. In der Entwicklung verwenden wir häufig den MD5-Algorithmus, um den Hash-Wert der Daten zu erhalten. Der Algorithmus gibt 128-Bit-Daten (16 Bytes) zurück. Um die Anzeige seines Werts zu erleichtern, wird üblicherweise eine hexadezimale Darstellung verwendet. Das Ergebnis ist eine bekannte 32-Bit-Zeichenfolge (ohne Berücksichtigung der Groß- und Kleinschreibung). Die 32-Byte-lange Zeichenfolge ist nicht das unvermeidliche Ergebnis des MD5-Algorithmus, 16-Byte-Daten sind sein Wesen. Wenn Sie möchten, können Sie eine Zahl kleiner als 2^128 verwenden, um das Hash-Ergebnis darzustellen, oder Sie können die 16 Bytes als Ergebnis mit Base64 kodieren. Daher ist die Beziehung zwischen dem häufig verwendeten 32-Bit-Hash-Wert und den von MD5 zurückgegebenen 16 Bytes: Eines ist die Zeichendarstellung und das andere ist sein Wesen (Zeichenarray) (der zweite Parameterwert der MD5-Funktion von PHP ist wahr). Abschnittsdaten mit 16 Zeichen erhalten oder der dritte Parameter der Hash-Funktion wahr ist).
Verwandte Konzepte umfassen Bytereihenfolge, Zeichenkodierung usw., auf die in diesem Artikel nicht näher eingegangen wird.
Einführung
Es gibt Dutzende von Funktionen in PHP, die auf die Verarbeitung von Zeichenfolgen spezialisiert sind. Wenn man reguläre, zeitliche und andere Funktionen hinzufügt, gibt es nicht weniger als hundert Funktionen für Zeichenfolgen Verarbeitung. Im Gegensatz dazu ist die Byteverarbeitung nicht beliebt und es gibt nur wenige verwandte Funktionen. Zusätzlich zu den häufig verwendeten ord/chr
, den von der Hash-Verschlüsselungsfunktion zurückgegebenen Originalbytes und den openssl_random_pseudo_bytes
und anderen Funktionen der OpenSSL-Bibliothek , die tatsächlich Bytes verarbeiten oder zurückgeben, sind dies die beiden wichtigsten Byteverarbeitungsfunktionen pack
und unpack
.
Dieser Abschnitt führt zur Verwendung der pack
-Funktion aus dem Problem.
Stellen Sie sich eine einfache Frage vor: Wie wird die ultimative Antwort auf das Universum 42 im Speicher dargestellt (oder wie erhält man sein Byte-Array)?
Da 42 eine Ganzzahl ist, kann seine Bytegröße je nach Hardware 1, 2, 4, 8 usw. betragen. Hier begrenzen wir eine Ganzzahl auf die Belegung von 4 Bytes, daher lautet die äquivalente Formulierung des Problems: Wie konvertiere ich eine Ganzzahl in ein Byte-Array (native Reihenfolge, 4 Bytes)?
Da es sich um mehrere Bytes handelt, muss die Frage der Bytereihenfolge berücksichtigt werden. 42 überschreitet 255 nicht und belegt nur ein Byte, sodass die anderen drei Bytes alle 0 sind. Auf dieser Grundlage können wir die Schlussfolgerung ziehen: Wenn es Big-Endian ist (das niederwertige Byte wird in der höherwertigen Adresse gespeichert), sind die vier Bytes: 0 0 0 42; ist Little-Endian, das Ergebnis ist: 42 0 0 0.
Woher wissen Sie dann die Bytereihenfolge der Maschine? PHP bietet keine entsprechenden Funktionen und kann auch nicht direkt auf Adressen und Bytedaten wie die C
-Sprache zugreifen. Wie kann das allmächtige PHP die Byte-Reihenfolge festlegen oder die Konvertierung von Daten in Bytes abschließen?
Auf PHP-Anwendungsebene ist die Konvertierung von Daten in Bytes (Arrays) ein besonderes Ereignis für pack
, und die Konvertierung von Bytes (Arrays) in Daten ist ein besonderes Ereignis Veranstaltung für unpack
. Abgesehen von diesen beiden Funktionen ist es fast unmöglich, Byte-Arrays (oder Binärdaten) in Daten umzuwandeln (bitte geben Sie mir nach Möglichkeit einen Rat).
现在我们用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 )
为何?官方文档上如是说:
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数据库文件等场合,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。
总结
Neben der Speicherzuweisung sind die wichtigsten Systemaufrufe das Lesen und Schreiben von Dateien sowie die Netzwerkverbindung, und die wesentlichen Betriebsobjekte beider sind Byteströme. pack
und unpack
bieten PHP die Möglichkeit, Low-Level-Byte-Operationen durchzuführen, was bei der Binärdatenverarbeitung sehr nützlich ist. PHP-Entwickler, die daran interessiert sind, aus der Webprogrammierung auszusteigen, sollten diese beiden Funktionen beherrschen.
Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in die Pack- und Entpack-Funktion in PHP (mit Code). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!