# 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); // 发送数据
}
```
- SD3.X简介
- 捐赠SD项目
- VIP服务
- 基础篇
- 搭建环境
- 使用Composer安装/更新SD框架
- 启动命令
- 开发注意事项
- 框架配置
- 配置文件夹
- server.php
- ports.php
- business.php
- mysql.php
- redis.php
- timerTask.php
- log.php
- consul.php
- catCache.php
- client.php
- 自定义配置
- 框架入口
- MVC架构
- 加载器-Loader
- 控制器-Controller
- 模型-Model
- 视图-View
- 同步任务-Task
- 封装器
- Swoole编程指南-EOF协议
- Swoole编程指南-固定包头协议
- 封装器-Pack
- 路由器
- TCP相关
- 绑定UID
- Send系列
- Sub/Pub
- 获取服务器信息
- Http相关
- HttpInput
- HttpOutput
- 默认路由规则
- WebSocket相关
- 使用SSL
- 公共函数
- 进阶篇
- 内核优化
- 封装器路由器原理剖析
- 对象池
- 上下文-Context
- 中间件
- 进程管理
- 创建自定义进程
- 进程间RPC
- 自定义进程如何使用连接池
- 异步连接池
- Redis
- Mysql
- Mqtt
- HttpClient
- Client
- AMQP
- RPC
- 日志工具-GrayLog
- 微服务-Consul
- Consul基础
- 搭建Consul服务器
- SD中Consul配置
- 微服务
- 选举-Leader
- Consul动态配置定时任务
- 熔断与降级
- 集群-Cluster
- 高速缓存-CatCache
- 万物-Actor
- Actor原型
- Actor的创建
- Actor间的通讯
- 消息派发-EventDispatcher
- 延迟队列-TimerCallBack
- 协程
- 订阅与发布
- MQTT简易服务器
- AMQP异步任务调度
- 自定义命令-Console
- 调试工具Channel
- 特别注意事项
- 日常问题总结
- 实践案例
- 物联网自定义协议
- Actor在游戏的应用
- Mongodb以及一些同步扩展的使用
- 自定义进程使用MQTT客户端
- 开发者工具
- SDHelper