# Swoole的自定義協議功能的使用
---
[TOC=3,4]
---
### **1.為什么要提供自定義協議**
熟悉TCP通信的朋友都會知道,TCP是一個流式協議。客戶端向服務器發送的一段數據,可能并不會被服務器一次就完整的收到;客戶端向服務器發送的多段數據,可能服務器一次就收到了全部的數據。而實際應用中,我們希望在服務器端能一次接收**一段完整的**數據,不多也不少。傳統的TCP服務器中,往往需要由程序員維護一個緩存區,先將讀到的數據放進緩存區中,然后再通過預先設定好的協議內容,來區分一段完整數據的開頭、長度和結尾,并將一段完整的數據交給邏輯部分處理。這就是自定義協議的功能。
而在Swoole中,已經在底層實現了一個數據緩存區,并內置好了幾個常用的協議類型,直接在底層做好了數據的拆分,保證了在onReceive回調函數中,一定能收到一個(或數個)完整的數據段。數據緩存區的大小可以通過配置選項[package_max_length](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#19package_max_length)來控制。下面我就將講解如何使用這些內置協議。
### **2.EOF標記型協議**
第一個比較常用的協議就是EOF標記協議。協議的內容是通過規定一個一定不會出現在正常數據中的字符或者字符串,用這個來標記一段完整數據的結尾。這樣,只要發現這個結尾,就可以認定之前的數據已經結束,可以開始接收一個新的數據段了。<br>
在Swoole中,可以通過[open_eof_check](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#13open_eof_check)和[package_eof](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#14package_eof)兩個配置項來開啟。其中,[open_eof_check](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#13open_eof_check)指定開啟了EOF檢測,[package_eof](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#14package_eof)指定了具體的EOF標記。通過這兩個選項,Swoole底層就會自動根據EOF標記來緩存和拆分收到的數據包。示例如下:
```php
$this->serv->set(array(
'package_max_length' => 8192,
'open_eof_check'=> true,
'package_eof' => "\r\n"
));
```
就這樣,swoole就已經開啟了EOF標記協議的解析。那么讓我們來測試一下效果:<br>
服務器這邊:<br>
```php
// Server
public function onReceive( swoole_server $serv, $fd, $from_id, $data ) {
echo "Get Message From Client {$fd}:{$data}\n";
}
```
客戶端這邊:<br>
```php
$msg_eof = "This is a Msg\r\n";
$i = 0;
while( $i < 100 ) {
$this->client->send( $msg_eof );
$i ++;
}
```
然后運行一下,你會發現:哎不對啊,為什么還是一次收到了好多數據啊!<br>
這是因為,在Swoole中,采用的不是遍歷識別的方法,而只是簡單的檢查每一次接收到的數據的末尾是不是定義好的EOF標記。因此,在開啟EOF檢測后,onReceive回調中還是可能會一次收到多個數據包。<br>
這要怎么辦?你會發現,雖然是多個數據包,但是實際上收到的是N個完整的數據片段,那就只需要根據EOF把每個包再拆出來,一個個處理就好啦。<br>
修改后的服務器端代碼如下:<br>
```php
public function onReceive( swoole_server $serv, $fd, $from_id, $data ) {
$data_list = explode("\r\n", $data);
foreach ($data_list as $msg) {
if( !empty($msg) ) {
echo "Get Message From Client {$fd}:{$msg}\n";
}
}
}
```
再次運行,妥了~<br>
[點此查看完整實例](https://github.com/LinkedDestiny/swoole-doc/blob/master/src/05/swoole_eof_server.php)<br>
另外,如果擔心這樣運行多段數據會長時間占用Worker,可以采用把數據+fd轉發給Task進程的做法。如何轉發請讀者自己嘗試實現。<br>
### **3.固定包頭類型協議**
固定包頭協議是在實際應用中最常用的協議。該協議的內容是規定一個固定長度的包頭,在包頭的固定位置有一個指定好的字段存放了后續數據的實際長度。這樣,服務器端可以先讀取固定長度的數據,從中提取出長度,然后再讀取指定長度的數據,即可獲取一段完整的數據。<br>
在Swoole中,同樣提供了固定包頭的協議格式。需要注意的是,Swoole只允許二進制形式的包頭,因此,需要使用pack、unpack來打包、解包。<br>
通過設置[open_length_check](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#15open_length_check)選項,即可打開固定包頭協議解析功能。此外還有[package_length_offset](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#16package_length_offset),[package_body_offset](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#17package_body_offset)和[package_length_type](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#18package_length_type)三個配置項用于控制解析功能。[package_length_offset](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#16package_length_offset)規定了包頭中第幾個字節開始是長度字段,[package_body_offset](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#17package_body_offset)規定了包頭的長度,[package_length_type](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#18package_length_type)規定了長度字段的類型。<br>
具體設置如下:<br>
```php
$this->serv->set(array(
'package_max_length' => 8192,
'open_length_check'=> true,
'package_length_offset' => 0,
'package_body_offset' => 4,
'package_length_type' => 'N'
));
```
具體如何設置這些參數請參考[文檔](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server配置選項.md)<br>
OK,廢話不多講,直接上實例:<br>
服務器端:<br>
```php
public function onReceive( swoole_server $serv, $fd, $from_id, $data ) {
$length = unpack("N" , $data)[1];
echo "Length = {$length}\n";
$msg = substr($data,-$length);
echo "Get Message From Client {$fd}:{$msg}\n";
}
```
客戶端:<br>
```php
$msg_length = pack("N" , strlen($msg_normal) ). $msg_normal;
$i = 0;
while( $i < 100 ) {
$this->client->send( $msg_length );
$i ++;
}
```
直接運行,Perfect!<br>
[點此查看完整實例](https://github.com/LinkedDestiny/swoole-doc/blob/master/src/05/swoole_length_check_server.php)<br>
[點此查看其他相關源碼](https://github.com/LinkedDestiny/swoole-doc/blob/master/src/05)<br>
### **4.特別篇:Http協議-Swoole內置的http_server**
從Swoole-1.7.7-stable開始,Swoole在內部封裝并實現了一個Http服務器。是的,沒錯,再也不用在PHP層緩存和解析http協議了,Swoole直接內置Http服務器了。<br>
創建一個swoole_http_server的代碼如下:<br>
```php
$http = new swoole_http_server("127.0.0.1", 9501);
$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {
$response->end("<h1>Hello Swoole.</h1>");
});
$http->start();
```
只需創建一個swoole_http_server對象并設置onRequest回調函數,即可實現一個http服務器。<br>
在onRequest回調中,共有兩個參數。參數`$request`存放了來自客戶端的請求,包括Http請求的頭部信息、Http請求相關的服務器信息、Http請求的GET和POST參數以及HTTP請求攜帶的COOKIE信息。參數`$response`用于發送數據給客戶端,可以通過該參數設置HTTP響應的Header信息、cookie信息和狀態碼。<br>
此外,swoole_http_server還提供WebSocket功能,使用此功能需要設置onMessage回調函數,如下:<br>
```php
$http_server->on('message', function(swoole_http_request $request, swoole_http_response $response) {
echo $request->message;
$response->message(json_encode(array("data1", "data2")));
})
```
通過`$request->message`獲取WebSocket發送來的消息,再通過`$response->message()`回復消息即可。<br>
[點此查看完整實例](https://github.com/LinkedDestiny/swoole-doc/blob/master/src/05/swoole_http_server.php)<br>
[點此查看swoole_http_server相關文檔](server/14.swoole_http_server.md)<br>
(最后做個小廣告,經過嘗試,已經初步將php的Yaf框架移植到了swoole_http_server上,經過測試,swoole-yaf的性能遠遠超過了nginx+php-fpm+yaf的性能。<br>
項目地址:https://github.com/LinkedDestiny/swoole-yaf<br>
我將繼續不斷完善這個項目,力爭能夠真正用于線上項目)
- swoole簡介
- swoole功能概述
- 序章
- 開發必讀
- 1 環境搭建
- 1.1 環境搭建
- 1.2 搭建Echo服務器
- 2 初識Swoole
- 2.1 Worker進程
- 2.2 TaskWorker進程
- 2.3 Timer定時器
- 2.4 Process進程
- 2.5 Table內存表
- 2.6 多端口監聽
- 2.7 sendfile文件支持
- 2.8 SSL支持
- 2.9 熱重啟
- 2.10 http_server
- 附錄*server配置
- 附錄*server函數
- 附錄*server屬性
- 附錄*server回調函數
- 附錄*server高級特性
- 心跳檢測
- 3 Swoole協議
- 3.1 EOF協議
- 3.2 固定包頭協議
- 3.3 Http協議
- 3.4 WebSocket協議
- 3.5 MTQQ協議
- 內置http_server
- 內置websocket_server
- Swoole\Redis\Server
- 4 Swoole異步IO
- 4.1 AsyncIO
- 異步文件系統IO
- swoole_async_readfile
- swoole_async_writefile
- swoole_async_read
- swoole_async_write
- 5 swoole異步客戶端
- ws_client
- http_client
- mysql_client
- redis_client
- tcp_client
- http2_client
- 6 swoole協程
- Swoole\Coroutine\Http\Client
- Swoole\Coroutine\MySQL
- Swoole\Coroutine\Redis
- Coroutine\PostgreSQL
- Swoole\Coroutine\Client
- Swoole\Coroutine\Socket
- Swoole\Coroutine\Channel
- Coroutine
- Swoole\Coroutine::create
- Swoole\Coroutine::resume
- Swoole\Coroutine::suspend
- Swoole\Coroutine::sleep
- Coroutine::getaddrinfo
- Coroutine::gethostbyname
- swoole_async_dns_lookup_coro
- Swoole\Coroutine::getuid
- getDefer
- setDefer
- recv
- Coroutine::stats
- Coroutine::fread
- Coroutine::fget
- Coroutine::fwrite
- Coroutine::readFIle
- Coroutine::writeFIle
- Coroutine::exec
- 7 swoole_process
- process::construct
- process::start
- process::name
- process::signal
- process::setaffinity
- process::exit
- process::kill
- process::daemon
- process->exec
- process::wait
- process::alarm
- 8 swoole定時器
- swoole_timer_tick
- swoole_timer_after
- swoole_timer_clear
- 9 swoole_event
- swoole_event_add
- swoole_event_set
- swoole_event_del
- swoole_event_wait
- swoole_event_defer
- swoole_event_write
- swoole_event_exit
- swoole提供的function
- 常見問題
- 客戶端鏈接失敗原因
- 如何設置進程數
- 如何實現異步任務
- 如何選擇swoole三種模式
- php中哪些函數是阻塞的
- 是否可以共用1個redis或mysql連接
- 如何在回調函數中訪問外部的變量
- 為什么不要send完后立即close
- 不同的Server程序實例間如何通信
- MySQL的連接池、異步、斷線重連
- 在php-fpm或apache中使用swoole
- 學習Swoole需要掌握哪些基礎知識
- 在phpinfo中有在php-m中沒有
- 同步阻塞與異步非阻塞選擇
- CURL發送POST請求服務器端超時
- 附錄
- 預定義常量
- 內核參數調優
- php四種回調寫法
- 守護進程程序常用數據結構
- swoole生命周期
- swoole_server中內存管理機制
- 使用jemalloc優化swoole內存分配性能
- Reactor、Worker、Task的關系
- Manager進程
- Swoole的實現
- Reactor線程
- 安裝擴展
- swoole-worker手冊
- swoole相關開源項目
- 寫在后面的話
- 版本更新記錄
- 4.0