如何定制協(xié)議
實際上制定自己的協(xié)議是比較簡單的事情。簡單的協(xié)議一般包含兩部分:
- 區(qū)分數(shù)據(jù)邊界的標識
- 數(shù)據(jù)格式定義
一個例子
協(xié)議定義
這里假設(shè)區(qū)分數(shù)據(jù)邊界的標識為換行符"\n"(注意請求數(shù)據(jù)本身內(nèi)部不能包含換行符),數(shù)據(jù)格式為Json,例如下面是一個符合這個規(guī)則的請求包。
{"type":"message","content":"hello"}
注意上面的請求數(shù)據(jù)末尾有一個換行字符(在PHP中用雙引號字符串"\n"表示),代表一個請求的結(jié)束。
實現(xiàn)步驟
在Workerman中如果要實現(xiàn)上面的協(xié)議,假設(shè)協(xié)議的名字叫JsonNL,所在項目為MyApp,則需要以下步驟
1、協(xié)議文件放到項目的Protocols文件夾,例如文件MyApp/Protocols/JsonNL.php
2、實現(xiàn)JsonNL類,以namespace Protocols;
為命名空間,必須實現(xiàn)三個靜態(tài)方法分別為 input、encode、decode
注意:workerman會自動調(diào)用這三個靜態(tài)方法,用來實現(xiàn)分包、解包、打包。具體流程參考下面執(zhí)行流程說明。
workerman與協(xié)議類交互流程
1、假設(shè)客戶端發(fā)送一個數(shù)據(jù)包給服務(wù)端,服務(wù)端收到數(shù)據(jù)(可能是部分數(shù)據(jù))后會立刻調(diào)用協(xié)議的input
方法,用來檢測這包的長度,input
方法返回長度值$length
給workerman框架。
2、workerman框架得到這個$length
值后判斷當前數(shù)據(jù)緩沖區(qū)中是否已經(jīng)接收到$length
長度的數(shù)據(jù),如果沒有就會繼續(xù)等待數(shù)據(jù),直到緩沖區(qū)中的數(shù)據(jù)長度不小于$length
。
4、緩沖區(qū)的數(shù)據(jù)長度足夠后,workerman就會從緩沖區(qū)截取出$length
長度的數(shù)據(jù)(即分包),并調(diào)用協(xié)議的decode
方法解包,解包后的數(shù)據(jù)為$data
。
3、解包后workerman將數(shù)據(jù)$data
以回調(diào)onMessage($connection, $data)
的形式傳遞給業(yè)務(wù),業(yè)務(wù)在onMessage里就可以使用$data
變量得到客戶端發(fā)來的完整并且已經(jīng)解包的數(shù)據(jù)了。
4、當onMessage
里業(yè)務(wù)需要通過調(diào)用$connection->send($buffer)
方法給客戶端發(fā)送數(shù)據(jù)時,workerman會自動利用協(xié)議的encode
方法將$buffer
打包后再發(fā)給客戶端。
具體實現(xiàn)
MyApp/Protocols/JsonNL.php的實現(xiàn)
namespace Protocols;
class JsonNL
{
/**
* 檢查包的完整性
* 如果能夠得到包長,則返回包的在buffer中的長度,否則返回0繼續(xù)等待數(shù)據(jù)
* 如果協(xié)議有問題,則可以返回-1,當前客戶端連接會因此斷開
* @param string $buffer
* @return int
*/
public static function input($buffer)
{
// 獲得換行字符"\n"位置
$pos = strpos($buffer, "\n");
// 沒有換行符,無法得知包長,返回0繼續(xù)等待數(shù)據(jù)
if($pos === false)
{
return 0;
}
// 有換行符,返回當前包長(包含換行符)
return $pos+1;
}
/**
* 打包,當向客戶端發(fā)送數(shù)據(jù)的時候會自動調(diào)用
* @param string $buffer
* @return string
*/
public static function encode($buffer)
{
// json序列化,并加上換行符作為請求結(jié)束的標記
return json_encode($buffer)."\n";
}
/**
* 解包,當接收到的數(shù)據(jù)字節(jié)數(shù)等于input返回的值(大于0的值)自動調(diào)用
* 并傳遞給onMessage回調(diào)函數(shù)的$data參數(shù)
* @param string $buffer
* @return string
*/
public static function decode($buffer)
{
// 去掉換行,還原成數(shù)組
return json_decode(trim($buffer), true);
}
}
至此,JsonNL協(xié)議實現(xiàn)完畢,可以在MyApp項目中使用,使用方法例如下面
文件:MyApp\start.php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
$json_worker = new Worker('JsonNL://0.0.0.0:1234');
$json_worker->onMessage = function(TcpConnection $connection, $data) {
// $data就是客戶端傳來的數(shù)據(jù),數(shù)據(jù)已經(jīng)經(jīng)過JsonNL::decode處理過
echo $data;
// $connection->send的數(shù)據(jù)會自動調(diào)用JsonNL::encode方法打包,然后發(fā)往客戶端
$connection->send(array('code'=>0, 'msg'=>'ok'));
};
Worker::runAll();
...
提示
workerman會嘗試加載Protocols
命名空間下的協(xié)議,例如new Worker('JsonNL://0.0.0.0:1234')
會嘗試加載Protocols\JsonNL
協(xié)議。
如果報錯Class 'Protocols\JsonNL' not found
,請參考自動加載實現(xiàn)自動加載。
協(xié)議接口說明
在Workerman中開發(fā)的協(xié)議類必須實現(xiàn)三個靜態(tài)方法,input、encode、decode,協(xié)議接口說明見Workerman/Protocols/ProtocolInterface.php,定義如下:
namespace Workerman\Protocols;
use \Workerman\Connection\ConnectionInterface;
/**
* Protocol interface
* @author walkor <walkor@workerman.net>
*/
interface ProtocolInterface
{
/**
* 用于在接收到的recv_buffer中分包
*
* 如果可以在$recv_buffer中得到請求包的長度則返回整個包的長度
* 否則返回0,表示需要更多的數(shù)據(jù)才能得到當前請求包的長度
* 如果返回-1,則代表錯誤的請求,則連接會斷開
*
* @param ConnectionInterface $connection
* @param string $recv_buffer
* @return int
*/
public static function input($recv_buffer, ConnectionInterface $connection);
/**
* 用于請求解包
*
* input返回值大于0,并且Workerman收到了足夠的數(shù)據(jù),則自動調(diào)用decode
* 然后觸發(fā)onMessage回調(diào),并將decode解碼后的數(shù)據(jù)傳遞給onMessage回調(diào)的第二個參數(shù)
* 也就是說當收到完整的客戶端請求時,會自動調(diào)用decode解碼,無需業(yè)務(wù)代碼中手動調(diào)用
* @param ConnectionInterface $connection
* @param string $recv_buffer
*/
public static function decode($recv_buffer, ConnectionInterface $connection);
/**
* 用于請求打包
*
* 當需要向客戶端發(fā)送數(shù)據(jù)即調(diào)用$connection->send($data);時
* 會自動把$data用encode打包一次,變成符合協(xié)議的數(shù)據(jù)格式,然后再發(fā)送給客戶端
* 也就是說發(fā)送給客戶端的數(shù)據(jù)會自動encode打包,無需業(yè)務(wù)代碼中手動調(diào)用
* @param ConnectionInterface $connection
* @param mixed $data
*/
public static function encode($data, ConnectionInterface $connection);
}
注意:
Workerman中沒有嚴格要求協(xié)議類必須基于ProtocolInterface實現(xiàn),實際上協(xié)議類只要類包含了input、encode、decode三個靜態(tài)方法即可。