# 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