PHP实现 struct结构体

还是那个朋友,在用 Workman 开发 TCP协议 的项目。TCP客户端发过来的流,整数啥的好解析,遇到解析小数蒙圈了。百度一下,竟然没找出来方法,最终使用unpack解决。

直接写,真头疼,很容易出错,模仿C语言的struct自己造了一个车子,这应该是最优雅的解决方案了(自我陶醉…)。

封装成了类,代码如下:

<?php
	 
/**
 * 类名:StructPHP
 * 作者:mqycn
 * 博客:http://www.miaoqiyuan.cn
 * 源码:http://www.miaoqiyuan.cn/p/php-struct
 * 说明:PHP实现Struct,基于 pack/unpack
 *	  官方文档: http://php.net/manual/zh/function.pack.php
 *	  数据类型:
 *		a - NUL 填充的字符串
 *		A - SPACE 填充的字符串
 *		h - 十六进制字符串,低位在前
 *		H - 十六进制字符串,高位在前
 *		c - signed char
 *		C - unsigned char
 *		s - signed short(总是16位, machine 字节顺序)
 *		S - unsigned short(总是16位, machine 字节顺序)
 *		n - unsigned short(总是16位, big endian 字节顺序)
 *		v - unsigned short(总是16位, little endian 字节顺序)
 *		i - signed integer(取决于machine的大小和字节顺序)
 *		I - unsigned integer(取决于machine的大小和字节顺序)
 *		l - signed long(总是32位, machine 字节顺序)
 *		L - unsigned long(总是32位, machine 字节顺序)
 *		N - unsigned long(总是32位, big endian 字节顺序)
 *		V - unsigned long(总是32位, little endian 字节顺序)
 *		f - float(取决于 machine 的大小和表示)
 *		d - double(取决于 machine 的大小和表示)
 *		x - NUL 字节
 *		X - 备份一个字节
 *		Z - NUL 填充的字符串
 *		@ - NUL 填充绝对位置
 */
	 
class StructPHP{
 
	public static function decode($struct = array(), $bin = ''){
		$format = '';
		foreach( $struct as $key => $val ){
			$format .= '/' . $val . (is_numeric($key) ? '' : $key);
		}
		$format = substr($format, 1);
		if(strlen($bin) == 0){
			throw new Exception('传入的数据长度为0');
		}
		return unpack($format, $bin);
	}

	public static function encode_hex($hex){
		$hex = str_replace(' ', '', $hex);
		return self::encode(array('H' . strlen($hex)), array($hex));
	}

	public static function encode($struct = array(), $data = array() ){
		if( !is_array($struct) || !is_array($data) || count($struct) == 0 || count($struct) <> count($data) ){
			throw new Exception('结构体与数据长度不对应');
		}
		$bin = '';
		foreach( $struct as $key => $val ){
			$bin .= pack($val, $data[$key]);
		}
		return $bin;
	}
}

测试下,没有问题,测试代码如下:

<?php
require('struct.php');

//测试 结构体
$struct = array(
	'char' => 'C',
	'long' => 'L',
	'float' => 'f',
	'double' => 'd'
);
$data = array(
	'char' => 65,
	'long' => 77068320,
	'float' => 77.068320,
	'double' => 7706.8320,
);
$bin = StructPHP::encode($struct, $data);
$message = StructPHP::decode($struct, $bin);
var_dump($bin, $message);
/**
 *   string(17) "A 鴹?欱F扼継@"
 *   array(4) {
 *     ["char"]=>
 *     int(65)
 *     ["long"]=>
 *     int(77068320)
 *     ["float"]=>
 *     float(77.068321228027)
 *     ["double"]=>
 *     float(7706.832)
 *   }
*/


//不指定key(不建议,可以编码,解析时存在bug,可以通过 $struct 解析)
$struct2 = array('C', 'L', 'f', 'd');
$data2 = array(65, 77068320, 77.068320, 7706.8320);
$bin2 = StructPHP::encode($struct2, $data2);
var_dump($bin2, $bin === $bin2);
/**
 *   string(17) "A 鴹?欱F扼継@"
 *   bool(true)
*/

//不指定key,解析时可以通过指定key的 struct 解析
$message2 = StructPHP::decode($struct, $bin2);
var_dump($message2, $message == $message2);
/**
 *   array(4) {
 *     ["char"]=>
 *     int(65)
 *     ["long"]=>
 *     int(77068320)
 *     ["float"]=>
 *     float(77.068321228027)
 *     ["double"]=>
 *     float(7706.832)
 *   }
 *   bool(true)
*/

稍微封装下,就可以运用于实际项目中了

<?php
require('struct.php');

function get_struct( $type = '00' ){
    $type = strtoupper($type);
     
    //协议头
    $struct = array(
        'header' => 'H8',
        'type' => 'H2'
    );
     
    switch( $type ){
        case "00": //直接返回消息头
            break;
         
        case "F2": //心跳包
            /**
             * u16 Bat_volt -/- 电池电量4
             * U32 Step_num 记步数据上海欧孚通信技术有限1
             * U8 Signal_strength 信号
             */
            $struct['bat_volt'] = 's';
            $struct['setp_num'] = 'L';
            $struct['bat_volt'] = 's';
            break;
 
        case "03": //GPS上报
            /**
            * 8 Double lon -/- longitude
            * 8 Double lat latitude
            * 1 U8 north_south #N or S
            * 1 U8 east_west #E or W
            * 1 U8 status #A or V
            * 4 U32 Timestamp 时间戳
            */
            $struct['lon'] = 'd';
            $struct['lat'] = 'd';
            $struct['north_south'] = 'C';
            $struct['east_west'] = 'C';
            $struct['status'] = 'C';
            $struct['timestamp'] = 'L';
             
            break;
             
        default:
            echo("\n\nUNKNOW MESSAGE TYPE:" . $message['type'] . "\n\n");
            break;
    }
    if( $type != '00' ){
        //如果指定了协议,则存在签名
        $struct['ck_sum'] = 'H2';
    }
    return $struct;
}
 
function message_decode( $bin ){
    //分析 协议头
    $struct = get_struct();
    $message = StructPHP::decode($struct, $bin);
     
    //分析协议
    $struct = get_struct($message['type']);
    return StructPHP::decode($struct, $bin);
}

function hex_to_bin($hexstr){
	return pack("H*", str_replace(' ', '', $hexstr));
}

//测试:解析包
$bin = hex_to_bin("BD BD BD BD F2 A4 01 40 01 00 00 33");
var_dump(message_decode($bin));
/**
 *   array(5) {
 *     ["header"]=>
 *     string(8) "bdbdbdbd"
 *     ["type"]=>
 *     string(2) "f2"
 *     ["bat_volt"]=>
 *     int(420)
 *     ["setp_num"]=>
 *     int(320)
 *     ["ck_sum"]=>
 *     string(2) "33"
 *   }
 */

$bin = hex_to_bin("BD BD BD BD 03 64 7D F0 C7 DA 95 5D 40 1B 96 19 49 95 8D 41 40 4E 45 41 C2 6D F2 5A 5F");
var_dump(message_decode($bin));
/**
 *   array(9) {
 *     ["header"]=>
 *     string(8) "bdbdbdbd"
 *     ["type"]=>
 *     string(2) "03"
 *     ["lon"]=>
 *     float(118.34147833333)
 *     ["lat"]=>
 *     float(35.106118333333)
 *     ["north_south"]=>
 *     int(78)
 *     ["east_west"]=>
 *     int(69)
 *     ["status"]=>
 *     int(65)
 *     ["timestamp"]=>
 *     int(1525837250)
 *     ["ck_sum"]=>
 *     string(2) "5f"
 *   }
 */

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.