+ -
当前位置:首页 → 问答吧 → unpack和pack来解析字符串

unpack和pack来解析字符串

时间:2009-02-12

来源:互联网

今天也来先点东西。
pack和unpack

申明:如果您认为pack,unpack根本不在您的使用范围内或觉得它们没什么用,
那下面的东西大可不看

我们都知道,用于封装数据到字节流,或从字节流解析到我们需要的数据,
我们还知道,通常int占4个字节,char占1个字节,short占2个字节,这些看手册上的pack说明,还有很多很多

比如php跟java进行socket通讯的时候,php发送一段数据给java,(协议自定,这里假定类型10表示获取游戏邮件列表,10000表示获取的id)
socket_write($sock,pack('CN',10,10000),5);
//N和C 手册上都很清楚,分别为unsigned int,char,一共为5个字节

java接受到后,会返回一段数据,从中获得你所需要的,比如java先告诉你返回内容规则如下,1byte,2int
php可以如下获得
$arr=unpack('Csuccess/Nid/Ncount',$data);
这样就完成一次解析过程.

//以下举例都在utf8下完成
这里我们都没有提到字符串的发送,我们知道字符串在字节流里的存储方式是
前2个字节表示字符串的长度,后面表示字符串的具体内容(学过java的应该都了解),2个字节也就限制了发送长度最大为65536
因而我们要发送字符串需要如下:
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));
?>[/php]

需要注意的是
1:很多服务器都会用utf8编码的格式,所以我们的php文件也必须使用同样的编码,否则会出乱码,或其他问题
2:该函数我只处理了4种类行,并且参数用java的类型代替了unpack原来的参数类型(由于我的协议书上都写的java类型,否则我老是要看到int,就用N,看到short就用n多麻烦啊),如需处理其他类型,请自行修改

欢迎大家提出自己的见解,有问题请联系我:
[email protected]
或站内短信

作者: sunceenjoy   发布时间: 2009-02-12