计算机在存储或传输数据时都是以 bit 流的形式(二进制),文本文件和二进制文件的主要区别就是在于文本文件是有字符集的,ascii/utf8/utf16
等,读取时会将二进制流解码成对应的字符集字符。而二进制文件则简单的将数据做为二进制流处理,使用文本编辑器打开时,ascii
解码,1byte 1byte
的处理并,有的落在ascii
可打印字符中的就显示,没有落在其中的就是咱们看到的乱码了。php
例如编程
存储segmentfault
ascii 文本数据 hello 存储的二进制流以下 存储的是每一个字符的 ASCII 码 01101000 01100101 01101100 01101100 01101111
读取socket
按 ascii 文本文件读取 会依次读取 1byte 而后输出 ascii 码表对应的字符 为了便于比较咱们这里读取 4byte 的数据 01101000 01100101 01101100 01101100 h--------e--------l--------l-------
按二进制流文件读取 能够读取 4byte 做为 uint 解析 二进制:01101000 01100101 01101100 01101100 十进制:|------------1751477356-----------| 能够读取 2byte 做为 usint 解析 二进制:01101000 01100101 01101100 01101100 十进制:|-----26725-----| |-----27756-----|
因此,文本文件和二进制文件的不一样之处是解析方式不一样,文本文件须要按照自身字符集,截取相应的字节长度读取,ascii 1byte, utf8 英文 1byte/中文 3byte, unicode 2byte
的方式去一段段的读取解析,二进制文件的话并无表征性的字符集,你能够按本身的须要解析,好比第1 byte
位表明的什么(unsigned short int / short int / ascii?),第2~3 byte
位表明的什么,第n~k byte
位表明的什么。tcp
由于 ascii 的 char 和 int 的存储方式本质一致的(C语言里 char 类型本质就是 int),因此用多字节可能更好理解。编辑器
<?php // uft8 存储中文字符使用 3byte 这里咱们将其拆成字节码后分别获取对应的二进制 foreach (str_split("我") as $key => $chr) { echo sprintf("%08d", decbin(ord($char))) . ' '; } // result 11100110 10001000 10010001
因此,若是咱们使用 utf8
字符集处理 11100110 10001000 10010001
时,咱们获得的是 “我”。ui
若是咱们将其做为二进制文件处理的话获得的结果是int(15108241)
数值。spa
<?php // 将二进制字符串转为对应的十进制 echo bindec('111001101000100010010001'); //result int(15108241)
其实这俩货让我迷惑了好久,很长一段时间我都不明白他俩到底有什么用,输出结果不难理解,还被同事的代码带过节奏,由于我看到他常常在发送数据前经过 bin2hex
转化一下再发送,我惯性的认为难不成能够压缩数据量?code
而这一次,我很刻意的将 hex2bin
写在了 bin2hex
的前面。ci
hex2bin(hexString)
的做用是将字符串做为十六进制
的模式进行处理,如何处理,"68656c6c6f"
会被处理成"68" "65" "6c" "6c" "6f"
,而后转换成对应的二进制数值,"68"(注意是字符串 2bytes)
转为二进制数值是 01101000(注意是数值 1byte)
,输出至终端其实就是h(1bytes)
的,依次处理后,咱们成功的将10bytes
的字符串"68656c6c6f"
转换成了5bytes
的字符串"hello"
,只不过"hello"
并非咱们真正须要的数据,虽然你对它很亲切。
不要被"hello"
迷惑了,最初我看到它时很难把它和bin
联系到一块儿。"hello"
是你的编辑器对hex2bin
后的二进制流
进行处理时,把二进制流
当作文本
处理(它们是文本编辑器,天然会把数据做为文本处理),刚好能对应上可打印字符的ascii码
,就显示了"hello"
,因此说文本和二进制文件底层都是bit流
,看怎么处理了。
bin2hex(binString)
则是将待处理的数据的二进制bit串
进行16进制
转换,并返回相应的16进制形式的字符串
,这里的bin
是说会将其做为二进制流,转换成对应的十六进制流,而后再以对应的字符串方式返回。好比"h"
的二进制bit串
是01101000
,对应的十六进制是 0x68
,相应的字符串形式是"68"
,依次继续解包处理 "e" "l" "l" "o"
后获得的字符串就是"68656c6c6f"
。
因此咱们若是想传输字符集在'0-9a-f'
内的数据,可使用 hex2bin
打包数据至二进制流节省空间,而后传输完成后再经过bin2hex
解包得到源数据。
hex2bin(hexString)/bin2hex(binString)
对应 pack("H*", hexString)/unpack("H*", binString)
只要提供了socket
编程的语言,确定也同时提供了 pack/unpack
,pack/unpack
的主要做用是方便咱们将数据打包至二进制流,而后以二进制流的方式解包。
将十六进制的字符串打包至二进制流字符串,这里有个很微妙的理解,好比字符串"68" 2bytes
,若是做为十六进制则其对应的二进制是00110110
,对应的ascii
文本字符是"h" 1byte
,传输后咱们对h
进行解包转为十六进制获得"68"
,这种打包-传输-解包的方式节省了一半的传输量。
好比咱们有一串字符串数据"68656c6c6f"
,若是直接传输的话须要strlen("68656c6c6f") = 10 byte
,你会发现这串数据彻底符合十六进制模式,若是进行以下处理咱们能够节省了一半的传输数据量。
<?php // 符合十六进制的字符串 0-9a-f $data = "68656c6c6f"; echo "src data len:" . strlen($data) . PHP_EOL; // 发送端 将 16进制 的字符串打包至 2进制 $package = pack("H*", $data); //若是你输出 $package 你会发现它是 "hello" echo "packed data len:" . strlen($package) . PHP_EOL; // 传输的数据量为 strlen($package) 个字节 // 接收端解包 $data = unpack("H*", $package); var_dump($data);
以上功能能够用 hex2bin/bin2hex
实现
<?php // 符合十六进制的字符串 0-9a-f $data = "68656c6c6f"; foreach (str_split($data) as $char) { echo sprintf("%08d", decbin(ord($char))) . ' '; } echo "data len: " . strlen($data) * 8 . ' bits' . PHP_EOL; $package = hex2bin($data); foreach (str_split($data) as $char) { echo sprintf("%08d", decbin(ord($char))) . ' '; } echo "package len: " . strlen($package) * 8 . ' bits' . PHP_EOL; $data = bin2hex($package); var_dump($data);
其实你会发现做为打包传输的数据是hello
,这里可能有些反客为主的惯性思惟,由于大部分时间对咱们有意义的数据是hello
而不是 68656c6c6f
,但在这里68656c6c6f
才是咱们须要的数据,打包后获得的hello
反而是压缩传输的中间数据。
n
是无符号短整型,固定2bytes
,能够将0 ~ 65536
内的数值用固定2bytes
表示,好比 "65536"
用字符串表示须要5bytes
,以 n
模式打包至二进制后只须要 2bytes
,节省了传输空间。
N
是无符号整形,固定4bytes
,能够将0~4294967296
内的数值用固定4bytes
表示。
固然,可能你的数值字符串是"0~99"
的时候节省空间的优势并不明显,但另外一个优势一直存在,那就是长度固定
,在一些协议传输时dataLen . data
的包结构是很须要dataLen
是固定字节的数据。
好比
// package struct 00000000 00000001 00110110 |---dataLen 1---| |data h|
咱们取数据包 package 的前2bytes
解包后获得的数值就是数据包消息体的长度,如上所示长度为 1,后面的data
即为1bytes 'h'
。
在tcp
流式数据时是很必须的,由于若是没有包长度声明,发送端连续发送多条消息,可能会致使粘包
,接收端没办法正确的拆分消息:msg1msg2msg3
可能会被拆分红 "msg" "1msg2ms" "g3"
。
而声明了包长度后,咱们能够先读取固定长度的字节获取到消息体长度,再读取相应长度的消息体内容。
<?php $foo = "hello world"; $bar = "i am sqrt_cat"; $package = ""; // 使用 n 打包 固定2bytes $fooLenn = pack("n", strlen($foo)); $package = $fooLenn . $foo; $barLenn = pack("n", strlen($bar)); $package .= $barLenn . $bar; // 传输 $package "fooLen . foo . barLen . bar" // 取前 2bytes 按 n 解包 $fooLen = unpack("n", substr($package, 0, 2))[1]; // 使用包消息体长度定义读取消息体 // 从第 3byte 开始读 前 2bytes表示长度 $foo = substr($package, 2, $fooLen); echo $foo . PHP_EOL; // 0 ~ (2 + fooLen) - 1 字节序为 fooLen . foo // (2 + fooLen) ~ (2 + fooLen) + 2 - 1 为 barLen $barLen = unpack("n", substr($package, (2 + $fooLen), 2))[1]; $bar = substr($package, (2 + $fooLen) + 2, $barLen); echo $bar . PHP_EOL;
这样就能够灵活的解析流数据,在一些状况下还节省了空间。
其余模式你们能够参考 http://www.javashuo.com/article/p-cyrqapbz-he.html