任何一款擁有socket操作能力的語(yǔ)言都有一個(gè)專門用于組包的函數(shù),php也不例外!
用了很久php了卻很少有機(jī)會(huì)用php進(jìn)行一些二進(jìn)制操作。 最近用php寫一個(gè)socket客戶端連接一個(gè)用C++語(yǔ)言開發(fā)的游戲服務(wù)端。 服務(wù)器端開發(fā)人員使用了二進(jìn)制的形式來(lái)定義協(xié)議的格式。協(xié)議格式如下:
包頭(2bytes)+加密(1byte)+命令碼(2bytes)+幀內(nèi)容
1.包頭的內(nèi)容是記錄幀內(nèi)容的長(zhǎng)度;
2. 加密:0表示不加密,1表示加密;
3. 命令碼為服務(wù)端命令識(shí)別符號(hào);
一開始不了解php原來(lái)有pack可以來(lái)組裝二進(jìn)制包, 走了彎路,讓服務(wù)端開發(fā)人員用C語(yǔ)言幫忙開發(fā)了的幾個(gè)內(nèi)存操作函數(shù),按照協(xié)議規(guī)則返回二進(jìn)制包,然后我將這幾個(gè)方法編譯成一組擴(kuò)展函數(shù)供php使用。
話歸正題,本文是介紹如何使用pack和unpack這兩個(gè)方法的。php官方手冊(cè)舉例太少,不能很容易理解,特別是那些格式化參數(shù)的使用。
轉(zhuǎn)摘的參數(shù)中文說(shuō)明:
pack/unpack 的摸板字符字符 含義
a 一個(gè)填充空的字節(jié)串
A 一個(gè)填充空格的字節(jié)串
b 一個(gè)位串,在每個(gè)字節(jié)里位的順序都是升序
B 一個(gè)位串,在每個(gè)字節(jié)里位的順序都是降序
c 一個(gè)有符號(hào) char(8位整數(shù))值
C 一個(gè)無(wú)符號(hào) char(8位整數(shù))值;關(guān)于 Unicode 參閱 U
d 本機(jī)格式的雙精度浮點(diǎn)數(shù)
f 本機(jī)格式的單精度浮點(diǎn)數(shù)
h 一個(gè)十六進(jìn)制串,低四位在前
H 一個(gè)十六進(jìn)制串,高四位在前
i 一個(gè)有符號(hào)整數(shù)值,本機(jī)格式
I 一個(gè)無(wú)符號(hào)整數(shù)值,本機(jī)格式
l 一個(gè)有符號(hào)長(zhǎng)整形,總是 32 位
L 一個(gè)無(wú)符號(hào)長(zhǎng)整形,總是 32 位
n 一個(gè) 16位短整形,“網(wǎng)絡(luò)”字節(jié)序(大頭在前)
N 一個(gè) 32 位短整形,“網(wǎng)絡(luò)”字節(jié)序(大頭在前)
p 一個(gè)指向空結(jié)尾的字串的指針
P 一個(gè)指向定長(zhǎng)字串的指針
q 一個(gè)有符號(hào)四倍(64位整數(shù))值
Q 一個(gè)無(wú)符號(hào)四倍(64位整數(shù))值
s 一個(gè)有符號(hào)短整數(shù)值,總是 16 位
S 一個(gè)無(wú)符號(hào)短整數(shù)值,總是 16 位,
字節(jié)序跟機(jī)器芯片有關(guān)
u 一個(gè)無(wú)編碼的字串
U 一個(gè) Unicode 字符數(shù)字
v 一個(gè)“VAX”字節(jié)序(小頭在前)的 16 位短整數(shù)
V 一個(gè)“VAX”字節(jié)序(小頭在前)的 32 位短整數(shù)
w 一個(gè) BER 壓縮的整數(shù)
x 一個(gè)空字節(jié)(向前忽略一個(gè)字節(jié))
X 備份一個(gè)字節(jié)
Z 一個(gè)空結(jié)束的(和空填充的)字節(jié)串
@ 用空字節(jié)填充絕對(duì)位置
string pack ( string $format [, mixed $args [, mixed $...]] )
一些規(guī)則:
1.每個(gè)字母后面都可以跟著一個(gè)數(shù)字,表示 count(計(jì)數(shù)),如果 count 是一個(gè) * 表示剩下的所有東西。
2.如果你提供的參數(shù)比 $format 要求的少,pack 假設(shè)缺的都是空值。如果你提供的參數(shù)比 $format 要求的多,那么多余的參數(shù)被忽略。
下面還是用例子來(lái)說(shuō)明用法會(huì)容易理解一點(diǎn):
關(guān)于Pack:
下面的第一部分把數(shù)字值包裝成字節(jié):
$out = pack("CCCC", 65, 66, 67, 68); # $out 等于"ABCD"
$out = pack("C4", 65, 66, 67, 68); # 一樣的東西
下面的對(duì) Unicode 的循環(huán)字母做同樣的事情:
$foo = pack("U4", 0x24b6, 0x24b7, 0x24b8, 0x24b9);
下面的做類似的事情,增加了一些空:
$out = pack("CCxxCC", 65, 66, 67, 68); # $out 等于 "AB\0\0CD"
打包你的短整數(shù)并不意味著你就可移植了:
$out = pack("s2", 1, 2);
# 在小頭在前的機(jī)器上是 "\1\0\2\0"
# 在大頭在前的機(jī)器上是 "\0\1\0\2"
在二進(jìn)制和十六進(jìn)制包裝上,count 指的是位或者半字節(jié)的數(shù)量,而不是生成的字節(jié)數(shù)量:
$out = pack("B32", "...");
$out = pack("H8", "5065726c"); # 都生成“Perl”
a 域里的長(zhǎng)度只應(yīng)用于一個(gè)字串:
$out = pack("a4", "abcd", "x", "y", "z"); # "abcd"
要繞開這個(gè)限制,使用多倍聲明:
$out = pack("aaaa", "abcd", "x", "y", "z"); # "axyz"
$out = pack("a" x 4, "abcd", "x", "y", "z"); # "axyz"
a 格式做空填充:
$out = pack("a14", "abcdefg"); # " abcdefg\0\0\0\0\0\0"
關(guān)于unpack:
array unpack ( string $format, string $data )
$data = "010000020007";
unpack("Sint1/Cchar1/Sint2/Cchar2",$data);
## array('int1'=>1, 'char1'=>'0','int2'=>2,'char2'=>7);
最后本文開頭講到的協(xié)議使用pack/unpack 舉例程序代碼為 :
$lastact = pack('SCSa32a32',0x0040, 0x00, 0x0006, $username, $passwd );
unpack('Sint1/Cchar1/Sint2/Cchar2/',$lastmessage);