# Swoole编程指南-固定包头协议 [原文链接](http://www.catplanet.me/?id=13) [TOC] ## 什么是固定包头协议 固定包头协议是在需要发送的数据前,加上一段预先规约好长度和格式的数据体,在该数据体中存放有后续数据的相应信息(一般情况下,后续数据的长度字段是必填项)的协议,这样一段数据体则称之为协议包头。在TCP的数据流中,使用固定包头协议的数据流特征如下: > | len长度 | 数据 | len长度 | 数据 | ## 知识科普:字节序 这里需要说明一下字节序的问题。在内存中,一个整数由四个连续的字节序列表示。例如一个int类型的变量x,其内存地址为0x100,那么这个变量的四个字节会被存储在0x100,0x101,0x102,0x103。 字节序则决定了这个变量的内容在这四个地址中的存放顺序。通常根据字节排列顺序的不同,将字节序区分为大端字节序和小端字节序。这里假设该变量x的16进制表示为0x12345678,在大端字节序中,该变量将以如下形式存放在内存中: ... 0x100 0x101 0x102 0x103 ... ... 12 34 56 78 ... 而在小端字节序中,则以如下形式存放: ... 0x100 0x101 0x102 0x103 ... ... 78 56 34 12 ... 可以看到,以内存地址从小到大代表从低到高,并且将变量从左到右设定为从低到高,大端序列就是高位字节存放在高位,低位字节存放在低位,而小端序反之。 在机器中,使用大端序和小端序完全取决于硬件本身。因此,在不同的机器之间通信时,需要有一个统一的字节序来进行传输,由此诞生了网络字节序的概念。网络字节序一般采用大端序(IP协议指定),而机器字节序则由机器本身决定。两者之间需要借助系统函数进行转换。 ## 开启协议检测 在Swoole中,可以使用如下配置选项来开启固定包头协议功能: ``` $server->set([ 'open_length_check' => 1, // 开启协议解析 'package_length_type' => 'N', // 长度字段的类型 'package_length_offset' => 0, //第N个字节是包长度的值 'package_body_offset' => 4, //第N个字节开始计算长度 'package_max_length' => 2000000, //协议最大长度 ]); ``` 在这里,package_length_type规定了length长度字段的类型,这个类型等价于使用PHP的pack函数打包数据时使用的类型,具体类型如下表所示: | 类型 | 长度 | 说明 | 范围 | | --- | --- | --- | --- | | c | 1字节 | 有符号Char | -128 ~ 127 | | C | 1字节 | 无符号Char | 0 ~ 256 | | s | 2字节 | 有符号,机器字节序 | -32768 ~ 32767| | S | 2字节 | 无符号,机器字节序 | 0 ~ 65535| | n | 2字节 | 无符号,网络字节序 | 0 ~ 65535| | N | 4字节 | 无符号,网络字节序 | 0 ~ 4294967295| | l | 4字节 | 有符号,机器字节序 | -2147483648 ~ 2147483648| | L | 4字节| 无符号,机器字节序 | 0 ~ 4294967295| | v | 2字节 | 无符号,小端字节序 | 0 ~ 65535| | V | 4字节 | 无符号,小端字节序 | 0 ~ 4294967295| ## 实战 首先,我们规定如下的协议格式: | 序号| len长度 | 数据| | --- | --- | --- | --- | | 4字节| 4字节| 不定长| 其中,序号为从0开始的自增序号,返回的数据也带有同样的序号用于标记同一次请求。长度字段为后续数据的实际长度,因此,一个完整请求的长度为length + 8。 服务器每次接收到的数据中,从第四个字节开始是长度字段,类型为4字节无符号整型。从第八个字节开始是数据体,接收length个字节后结束,记为一个完整数据包。 服务器设置代码如下所示: ``` $serv->set(array( 'worker_num' => 1, 'open_length_check' => true, // 开启协议解析 'package_length_type' => 'N', // 长度字段的类型 'package_length_offset' => 4, //第4个字节开始是包长度 'package_body_offset' => 8, //第8个字节开始计算长度 'package_max_length' => 2000000, //协议最大长度 )); ``` 接收数据部分代码如下: ``` public function onReceive( swoole_server $serv, $fd, $from_id, $data ) { $head = unpack('NN', substr($data, 0 , 8)); // 获取包头 $body = substr($data, 8); // 获取数据 var_dump($body); $response = "Hello"; $num = $head[0]; $len = strlen($response); $content = pack('NN', $num, $len) . $response; // 拼接响应内容 $serv->send($fd, $content); // 发送数据 } ```