Swoole实现TCP协议分包

### 关于分包

> 以前习惯于用固定字符串来处理分包,这种更适合纯软件的交互,数据格式封装成json;

> 嵌入式硬件一般不会上传json的字符串,更习惯用十六进制字符表示,或者acsii码,所以协议也就需要用‘固定包头 + 包体协议’来处理。

### 固定包头

1. 这里也有两种方式,一种是上传的数据是swoole能直接解析的10进制或者字符串,这时候可以用unpack/pack来处理,比如:

    “`

        $client = new Swoole\Client(SWOOLE_SOCK_TCP);

        $data = “123456789012345678901234567890”;

        $type = 0x30;

        $uid = 0x123;

        $length = strlen($data);

        $serid = 0x15;

        $head = pack(“N4”, $type, $uid, $length, $serid);

        $body = pack(“a{$length}”, $data);

        $message = $head.$body;

        if ($client->connect(‘127.0.0.1’, 9502, -1)) {

            $client->send($message);

            echo $client->recv();

        }

        $client->close();

    “`

    “`

        $serv = new Swoole\Server(‘127.0.0.1’, 9502);

        $serv->set([

            ‘open_length_check’     => true,

            ‘package_max_length’    => 81920,

            ‘package_length_type’   => ‘N’,

            ‘package_length_offset’ => 8,

            ‘package_body_offset’   => 16,

        ]);

        $serv->on(‘connect’, function($server, $fd){

            echo $fd. ” Connect !”.PHP_EOL;

        });

        $serv->on(‘receive’, function($server, $fd, $from_id, $data){

            var_dump($data);            // 源数据

            $tmp = unpack(“Ntype/Nuid/Nlength”, $data);

            $unpacking = unpack(“Ntype/Nuid/Nlength/Nserid/a{$tmp[‘length’]}body”, $data);

            var_dump($unpacking);        // 解包后数据

            $server->send($fd, ” Server Receive Data: “. $unpacking[‘body’]);

        });

        $serv->on(‘close’, function($server){

        });

        $serv->start();

    “`

2. 还有一种是上传过来的数据是二进制数,需要自己用自定义回调处理长度。设置长度解析函数(package_length_func)。

    “`

        $server = new Swoole\Server(“127.0.0.1”, 9501);

        $server->set(array(

            ‘open_length_check’   => true,

            ‘dispatch_mode’       => 1,

            ‘package_length_func’ => function ($data) {

                if (strlen($data) < 8) {

                    return 0;

                }

                $length = intval(trim(substr($data, 0, 8)));

                if ($length <= 0) {

                    return -1;

                }

                return $length + 8;

            },

            ‘package_max_length’  => 2000000,  //协议最大长度

        ));

        $server->on(‘receive’, function (Swoole\Server $server, $fd, $reactor_id, $data) {

            var_dump($data);

            echo “#{$server->worker_id}>> received length=” . strlen($data) . “\n”;

        });

        $server->start();

    “`

    > 还有一种是上传过来的数据是二进制数,需要自己用自定义回调处理长度。设置长度解析函数(package_length_func)。

    “`

        ‘package_length_func’ => function ($data) {

            // 2进制转16进制,这个坑好久才反应过来

            $d = bin2hex($data);

            dump(date(‘Y-m-d H:i:s’) . ‘ >> ‘.$d);

            if (strlen($d) < 8) {

                return 0;

            }

            $length = substr($d, 4, 4);

            // echo ‘str length: ‘ . $length . “.\n”;

            // 十六进制转10进制,这里的处理可能会有问题,这里算的是字节位数,一个16进制数占用0.5字节

            $length = base_convert(trim($length, ‘0’), 16, 10);

            // echo ‘byte length: ‘ . $length . “.\n”;

            if ($length <= 0) {

                return -1;

            }

            return $length;

        }

    “`

### 其他问题

1. 一般协议都长这样

    > 起始域     长度域     版本域    序列号域     命令代CMD  数据域    校验和域

        AAF5 –     6300 –     10 –      00 –        6A00        – N –    00

2. 校验和算法大同小异,比如这样:<https://www.cnblogs.com/kala00k/p/12686664.html>

    “`

    // 计算校验和

    public static function getJyh($m){

        $dexArr = str_split($m, 2);

        foreach ($dexArr as $key => $value) {

            $dexArr[$key] = hexdec($value);

        }

        $ck2 = dechex(array_sum($dexArr));

        $check = substr($ck2, strlen($ck2) – 2, 2);

        if (strlen($check) == ‘1’) {

            $check = ‘0’.$check;

        }

        return $check;

    }

    “`

3. 进制转换的时候是需要用0来补足字节位数的

    “`

    public static function decto_bin($data)

    {

        $d = base_convert($data, 10, 16);

        if (strlen($d) == ‘1’) {

            $d = ‘0’ . $d;

        }

        if (strlen($d) < 4) {

            $num = 4 – strlen($d);

            $arr = [”, ‘0’, ’00’];

            $d .= $arr[$num];

        }

        return strtoupper($d);

    }

    “`

4. 十六进制转十进制的时候如果刚好是16的倍数需要处理0的问题,暂时没有遇到这样定义的先如下:

    “`

        // 这个解法在正好是16倍数的时候有问题,应该先拆成2组,理论上应该拆成这样的两两数组 $dexArr = str_split($m, 2);

        $cmd = base_convert(trim($cmd,’0′), 16, 10);

    “`