Swoole的自定義協(xié)議功能的使用

2018-02-24 16:13 更新

環(huán)境說明: 系統(tǒng):Ubuntu14.04 (安裝教程包括CentOS6.5)
PHP版本:PHP-5.5.10
swoole版本:1.7.8-alpha

1.為什么要提供自定義協(xié)議

熟悉TCP通信的朋友都會(huì)知道,TCP是一個(gè)流式協(xié)議??蛻舳讼蚍?wù)器發(fā)送的一段數(shù)據(jù),可能并不會(huì)被服務(wù)器一次就完整的收到;客戶端向服務(wù)器發(fā)送的多段數(shù)據(jù),可能服務(wù)器一次就收到了全部的數(shù)據(jù)。而實(shí)際應(yīng)用中,我們希望在服務(wù)器端能一次接收一段完整的數(shù)據(jù),不多也不少。傳統(tǒng)的TCP服務(wù)器中,往往需要由程序員維護(hù)一個(gè)緩存區(qū),先將讀到的數(shù)據(jù)放進(jìn)緩存區(qū)中,然后再通過預(yù)先設(shè)定好的協(xié)議內(nèi)容,來區(qū)分一段完整數(shù)據(jù)的開頭、長度和結(jié)尾,并將一段完整的數(shù)據(jù)交給邏輯部分處理。這就是自定義協(xié)議的功能。
而在Swoole中,已經(jīng)在底層實(shí)現(xiàn)了一個(gè)數(shù)據(jù)緩存區(qū),并內(nèi)置好了幾個(gè)常用的協(xié)議類型,直接在底層做好了數(shù)據(jù)的拆分,保證了在onReceive回調(diào)函數(shù)中,一定能收到一個(gè)(或數(shù)個(gè))完整的數(shù)據(jù)段。數(shù)據(jù)緩存區(qū)的大小可以通過配置選項(xiàng)package_max_length來控制。下面我就將講解如何使用這些內(nèi)置協(xié)議。

2.EOF標(biāo)記型協(xié)議

第一個(gè)比較常用的協(xié)議就是EOF標(biāo)記協(xié)議。協(xié)議的內(nèi)容是通過規(guī)定一個(gè)一定不會(huì)出現(xiàn)在正常數(shù)據(jù)中的字符或者字符串,用這個(gè)來標(biāo)記一段完整數(shù)據(jù)的結(jié)尾。這樣,只要發(fā)現(xiàn)這個(gè)結(jié)尾,就可以認(rèn)定之前的數(shù)據(jù)已經(jīng)結(jié)束,可以開始接收一個(gè)新的數(shù)據(jù)段了。
在Swoole中,可以通過open_eof_checkpackage_eof兩個(gè)配置項(xiàng)來開啟。其中,open_eof_check指定開啟了EOF檢測(cè),package_eof指定了具體的EOF標(biāo)記。通過這兩個(gè)選項(xiàng),Swoole底層就會(huì)自動(dòng)根據(jù)EOF標(biāo)記來緩存和拆分收到的數(shù)據(jù)包。示例如下:

$this->serv->set(array(
    'package_max_length' => 8192,
    'open_eof_check'=> true,
    'package_eof' => "\r\n"
));

就這樣,swoole就已經(jīng)開啟了EOF標(biāo)記協(xié)議的解析。那么讓我們來測(cè)試一下效果:
服務(wù)器這邊:

// Server
public function onReceive( swoole_server $serv, $fd, $from_id, $data ) {
    echo "Get Message From Client {$fd}:{$data}\n";
}

客戶端這邊:

$msg_eof = "This is a Msg\r\n";

$i = 0;
while( $i < 100 ) {
    $this->client->send( $msg_eof );
    $i ++;
}

然后運(yùn)行一下,你會(huì)發(fā)現(xiàn):哎不對(duì)啊,為什么還是一次收到了好多數(shù)據(jù)啊!
這是因?yàn)椋赟woole中,采用的不是遍歷識(shí)別的方法,而只是簡單的檢查每一次接收到的數(shù)據(jù)的末尾是不是定義好的EOF標(biāo)記。因此,在開啟EOF檢測(cè)后,onReceive回調(diào)中還是可能會(huì)一次收到多個(gè)數(shù)據(jù)包。
這要怎么辦?你會(huì)發(fā)現(xiàn),雖然是多個(gè)數(shù)據(jù)包,但是實(shí)際上收到的是N個(gè)完整的數(shù)據(jù)片段,那就只需要根據(jù)EOF把每個(gè)包再拆出來,一個(gè)個(gè)處理就好啦。
修改后的服務(wù)器端代碼如下:

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";
        }

    }
}

再次運(yùn)行,妥了~
點(diǎn)此查看完整實(shí)例
另外,如果擔(dān)心這樣運(yùn)行多段數(shù)據(jù)會(huì)長時(shí)間占用Worker,可以采用把數(shù)據(jù)+fd轉(zhuǎn)發(fā)給Task進(jìn)程的做法。如何轉(zhuǎn)發(fā)請(qǐng)讀者自己嘗試實(shí)現(xiàn)。

3.固定包頭類型協(xié)議

固定包頭協(xié)議是在實(shí)際應(yīng)用中最常用的協(xié)議。該協(xié)議的內(nèi)容是規(guī)定一個(gè)固定長度的包頭,在包頭的固定位置有一個(gè)指定好的字段存放了后續(xù)數(shù)據(jù)的實(shí)際長度。這樣,服務(wù)器端可以先讀取固定長度的數(shù)據(jù),從中提取出長度,然后再讀取指定長度的數(shù)據(jù),即可獲取一段完整的數(shù)據(jù)。
在Swoole中,同樣提供了固定包頭的協(xié)議格式。需要注意的是,Swoole只允許二進(jìn)制形式的包頭,因此,需要使用pack、unpack來打包、解包。
通過設(shè)置open_length_check選項(xiàng),即可打開固定包頭協(xié)議解析功能。此外還有package_length_offset,package_body_offsetpackage_length_type三個(gè)配置項(xiàng)用于控制解析功能。package_length_offset規(guī)定了包頭中第幾個(gè)字節(jié)開始是長度字段,package_body_offset規(guī)定了包頭的長度,package_length_type規(guī)定了長度字段的類型。
具體設(shè)置如下:

$this->serv->set(array(
    'package_max_length' => 8192,
    'open_length_check'=> true,
    'package_length_offset' => 0,
    'package_body_offset' => 4,
    'package_length_type' => 'N'
));

具體如何設(shè)置這些參數(shù)請(qǐng)參考文檔
OK,廢話不多講,直接上實(shí)例:
服務(wù)器端:

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";
}

客戶端:

$msg_length = pack("N" , strlen($msg_normal) ). $msg_normal;

$i = 0;
while( $i < 100 ) {
    $this->client->send( $msg_length );
    $i ++;
}

直接運(yùn)行,Perfect!
點(diǎn)此查看完整實(shí)例

點(diǎn)此查看其他相關(guān)源碼

4.特別篇:Http協(xié)議-Swoole內(nèi)置的http_server

從Swoole-1.7.7-stable開始,Swoole在內(nèi)部封裝并實(shí)現(xiàn)了一個(gè)Http服務(wù)器。是的,沒錯(cuò),再也不用在PHP層緩存和解析http協(xié)議了,Swoole直接內(nèi)置Http服務(wù)器了。
創(chuàng)建一個(gè)swoole_http_server的代碼如下:

$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();

只需創(chuàng)建一個(gè)swoole_http_server對(duì)象并設(shè)置onRequest回調(diào)函數(shù),即可實(shí)現(xiàn)一個(gè)http服務(wù)器。
在onRequest回調(diào)中,共有兩個(gè)參數(shù)。參數(shù)$request存放了來自客戶端的請(qǐng)求,包括Http請(qǐng)求的頭部信息、Http請(qǐng)求相關(guān)的服務(wù)器信息、Http請(qǐng)求的GET和POST參數(shù)以及HTTP請(qǐng)求攜帶的COOKIE信息。參數(shù)$response用于發(fā)送數(shù)據(jù)給客戶端,可以通過該參數(shù)設(shè)置HTTP響應(yīng)的Header信息、cookie信息和狀態(tài)碼。

此外,swoole_http_server還提供WebSocket功能,使用此功能需要設(shè)置onMessage回調(diào)函數(shù),如下:

$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發(fā)送來的消息,再通過$response->message()回復(fù)消息即可。
點(diǎn)此查看完整實(shí)例
點(diǎn)此查看swoole_http_server相關(guān)文檔

(最后做個(gè)小廣告,經(jīng)過嘗試,已經(jīng)初步將php的Yaf框架移植到了swoole_http_server上,經(jīng)過測(cè)試,swoole-yaf的性能遠(yuǎn)遠(yuǎn)超過了nginx+php-fpm+yaf的性能。
項(xiàng)目地址:https://github.com/LinkedDestiny/swoole-yaf
我將繼續(xù)不斷完善這個(gè)項(xiàng)目,力爭能夠真正用于線上項(xiàng)目)

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)