計算機在存儲或傳輸數據時都是以 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