php通过pack和unpack函数实现对二进制数据封装及解析
a一个填充空的字节串
A一个填充空格的字节串
b一个位串,在每个字节里位的顺序都是升序
B一个位串,在每个字节里位的顺序都是降序
C一个无符号char(8位整数)值;关于Unicode参阅U
f本机格式的单精度浮点数
h一个十六进制串,低四位在前
H一个十六进制串,高四位在前
i一个有符号整数值,本机格式
I一个无符号整数值,本机格式
l一个有符号长整形,总是32位
L一个无符号长整形,总是32位
N一个32位短整形,“网络”字节序(大头在前)
P一个指向定长字串的指针
q一个有符号四倍(64位整数)值
Q一个无符号四倍(64位整数)值
s一个有符号短整数值,总是16位
S一个无符号短整数值,总是16位,字节序跟机器芯片有关
u一个无编码的字串
v一个“VAX”字节序(小头在前)的16位短整数
V一个“VAX”字节序(小头在前)的32位短整数
x一个空字节(向前忽略一个字节)
X备份一个字节
Z一个空结束的(和空填充的)字节串
规则:
(1).每个字母后面都可以跟着一个数字,表示count(计数,如果count是一个*表示剩下的所有东西。
(2)如果你提供的参数比$format要求的少,pack假设缺的都是空值。如果你提供的参数比$format要求的多,那么多余的参数被忽略。
php跟java进行socket通讯的时候,php发送一段数据给java,(协议自定,这里假定类型10表示获取游戏邮件列表,10000表示获取的id)
socket_write($sock,pack('CN',10,10000),5);
java接受到后,会返回一段数据,从中获得你所需要的,比如java先告诉你返回内容规则如下:1 byte,2 int
php可以通过如下方式获得:
$arr=unpack('Csuccess/Nid/Ncount',$data);
这样就完成一次解析过程.
这里我们都没有提到字符串的发送,我们知道字符串在字节流里的存储方式是前2个字节表示字符串的长度,后面表示字符串的具体内容(学过java的应该都了解),2个字节也就限制了发送长度最大为65536,因而我们要发送字符串需要如下(以下举例都在utf8下完成):
function pack_str($str){ //如果是gbk,要转成utf8 // $str = iconv('gbk','utf-8',$str); $utflen = strlen($str); if ($utflen > 65535) die('too long'); $in .= pack('C2',$utflen>>8,$utflen>>0); return $in.$str; }
比如我们要向游戏服务器内发送一个公告:各位,服务器在1小时内重起!假设java要求这样的格式:协议号:int,标题,内容。我们就可以如下发送:
$in=pack('N',1000); $in.=pack_utf8('公告'); $in.=pack_utf8('各位,服务器在1小时内重起!');
这样就完成一次发送.同样如果我们需要读取游戏服务器的数据,比如用户资料,也会返回字符串,原理同上,先读2个字节获取长度,再根据长度来获取具体的内容,代码如下:
$crt_str =unpack("C{$crt_str_len}str",$data); for($ii=1;$ii<=$crt_str_len;$ii++){ $str .= chr($crt_str['str'.$ii]); }
$str就是我们要获取的中文,但是这样极其烦琐,如果有多个字符串的话,中间又包含了其他数据,比如返回为int,string,int,byte,string这样处理起来相当不便,于是提供下面的函数供大家参考:
<?php /* 由于我的程序经常跟java通信,所以此函数所使用的参数是用java里面的类型来填充的,并且只替换了经常用到的3个类型 C-->b(byte) n-->s(short) i-->N(int) 如不习惯或或觉得参数过少,请自行修改 */ function rs_unpack($parse,$data) { $parselen = strlen($parse); $parsepos = 0; $datapos = 0; $argc = 1; $ret = array(); while($parsepos<$parselen){ $dostr = false; $type = substr($parse,$parsepos++,1); switch($type){ case 'b': $size = 1; $argv .= 'C'; break; case 's': $size = 2; $argv .= 'n'; break; case 'i': $size = 4; $argv .= 'N'; break; case 'Z': $dostr = true; /*处理字符传之前的数据*/ $arr = unpack($argv,substr($data,$datapos,$argvlen)); $datapos += $argvlen; $argvlen = 0; $argv = ''; $ret = array_merge($ret,$arr); /*获取要解析的字符串的个数,并移动指针*/ if($parsepos<$parselen) $argc = intval(substr($parse,$parsepos)); if($argc==0) $argc = 1; while($parsepos<$parselen){ $type = substr($parse,$parsepos,1); if($type>='0'&&$type<='9'){ $parsepos++; }else{ break; } } /*获取字符串的命名*/ $namepos= $parsepos; $type = ''; while($parsepos<$parselen){ $type= substr($parse,$parsepos,1); $parsepos++; $namelen++; if($type=='/') break; } $strname = substr($parse,$namepos,$parsepos-$namepos-($type=='/'?1:0)); /*处理各个字符串*/ for($i=0;$i<$argc;$i++){ $str= ''; $crt_len_arr = unpack('nstr_len',substr($data,$datapos,2)); $datapos+= 2; $crt_str_len = $crt_len_arr['str_len']; $crt_str = unpack("C{$crt_str_len}str",substr($data,$datapos,$crt_str_len)); for($ii=1;$ii<=$crt_str_len;$ii++){ $str.= chr($crt_str['str'.$ii]); } $ret= array_merge($ret,array($strname.($argc>1?($i+1):'')=>$str)); $datapos+= $crt_str_len; } break; default: die('parse error'); } if($dostr) continue; /*获取数据长度*/ if($parsepos<$parselen) $argc = intval(substr($parse,$parsepos)); if($argc==0){ $argc = 1; }else{ /*unpack代码限制了只能200*/ if($argc>200) $argc= 200; } $argvlen+= $argc*$size; /*移动解析参数指针*/ while($parsepos<$parselen){ $type = substr($parse,$parsepos,1); $argv .= $type; $parsepos++; if($type=='/') break; } } if(!empty($argv)){ $ret = array_merge($ret,unpack($argv,substr($data,$datapos))); } return $ret; } function pack_str($str){ // $str = iconv('gbk','utf-8',$str); $utflen = strlen($str); if ($utflen > 65535) die('too long'); $in .= pack('C2',$utflen>>8,$utflen>>0); return $in.$str; } $in .= pack('C',10); $in .= pack_str("标题"); $in .= pack('C',10); $in .= pack_str("内容"); print_r(rs_unpack('bbyte/Zstr/be/Zstrw',$in)); /*比如java发送int,string,string,分别表示协议号,标题,内容 这里用php模拟发送的数据 */ $in = pack('N',1000); $in .= pack_str("公告"); $in .= pack_str("服务器在10分钟内重启!"); print_r(rs_unpack('i/Ztitle/Zcontent',$in)); print_r(rs_unpack('i/Z2str',$in)); ?>
需要注意的是:
(1)很多服务器都会用utf8编码的格式,所以我们的php文件也必须使用同样的编码,否则会出乱码,或其他问题
(2)该函数我只处理了4种类行,并且参数用java的类型代替了unpack原来的参数类型,如需处理其他类型,请自行修改。
2.其它举例:
例子1
<?php $data = "PHP"; print_r(unpack("C*",$data)); ?>
输出:
Array ( [1] => 80 [2] => 72 [3] => 80 )
例子 2
<?php $data = "PHP"; print_r(unpack("C*myint",$data)); ?>
输出:
Array ( [myint1] => 80 [myint2] => 72 [myint3] => 80 )
例子 3
<?php $bin = pack("c2n2",0x1234,0x5678,65,66); print_r(unpack("c2chars/n2int",$bin)); ?>
输出:
Array ( [chars1] => 52 [chars2] => 120 [int1] => 65 [int2] => 66 )