Swoole 系列之二(Websocket 要點講解)

Dennis_Ritchie發表於2020-03-10

前言

作為Swoole系列的第二篇,主要講解websocket在swoole中的使用問題,官方文件如下:https://wiki.swoole.com/#/websocket_server

今天我之所以需要講解websocket,因為不管是在PHP,還是在SpringBoot中,它都佔有一席之地。同時Swoole的官方文件個人覺得還是不清不楚的。

在這篇博文中,我不會展示如何使用如何使用websocket,至少在官方文件中已經展示過了,我所要做的就是對官方文件的一些補充

參考連結

《老司機帶你用 PHP 實現 Websocket 協議》

《RFC6455》

基本使用

下面的例子是Swoole的官方例項,你只需要複製貼上就可以了。

$server = new Swoole\WebSocket\Server("0.0.0.0", 9501);

$server->on('open', function (Swoole\WebSocket\Server $server, $request) {
    echo "server: handshake success with fd{$request->fd}\n";
});

$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
    echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
    $server->push($frame->fd, "this is server");
});

$server->on('close', function ($ser, $fd) {
    echo "client {$fd} closed\n";
});

$server->start();

例子很簡單,但是我要說的是open回撥中,$request->fd所代表的的套接字已經完成了Websocket的握手過程

在message回撥中,你永遠不需要檢查$frame->finish的值,如果你不知道這個屬性的值,你應該看看我寫的《老司機帶你用 PHP 實現 Websocket 協議》

FIN 位,也是整個片段的第一個位元組的最高位,他只能是 0 或者是 1,這個位的作用只有一個,如果它為 1,表示這個片段是整個訊息的最後一個片段,如果是 0,表示這個片段之後,還有其它的片段。是不是聽著直接懵逼了,啥是 片段?啥是 訊息?非常好,看來我裝逼的時候已經來臨了,廢話不多說。為了搞清楚這幾個概念,程式碼為敬

$frame->finish的值永遠都是1,因為Swoole官方已經為你合併過了,所以你不需要擔心,$frame->opcode的值也就是操作碼的值有多種

opcode 顧名思義就是操作碼,佔用第一個位元組的低四位,所以 opcode 可以代表 16 種不同的值。你是不是想問,opcode 是用來幹嘛的?
opcode 是用 來解析當前片段的載荷(攜帶的資料)的,具體的後面會再次說明。
0x00,表示當前片段是連續片段,這是啥意思呢?還記得上面討論 FIN 的時候,一條訊息被分割成多條片段?如果當前片段不是第一個,那麼 opcode 必須設定為 0。
0x01,表示當前片段所攜帶的資料是文字資料(記得最開始說的文字資料和二進位制資料的區別??),如果有多個片段的話,只需要在第一個片段設定該值,屬於同一條訊息中後面的片段,只需要設定為 0 即可。
0x02,表示當前片段所攜帶的資料是二進位制資料,如果有多個片段的話,只需要在第一個片段設定該值,屬於同一條訊息中後面的片段,只需要設定為 0 即可。
0x03-0x07,保留給將來使用,也就是說暫時還沒用到。
0x08,表示關閉 websocket 連線,這個後面我會再一次講到,先放著
0x09,傳送 Ping 片段,說白了,它主要是用來檢測遠端端點是否還存活,我想檢查我的物件是不是已經死了,但是這個片段可以攜帶資料,如果端點的一方傳送了 Ping,那麼接受方,必須返回 Pong 片段,用中國人的話來說,就是禮尚往來嘛。
0xA,傳送 Pong,用以回覆 Ping,是不是很簡單?
0xB-F,保留給將來使用,也就是說暫時還沒用到。

這個值在Swoole中由於被攔截掉了,啥意思呢?就是你在你的程式中只能接受到1和2的操作碼,在Swoole中就是:

Swoole系列之二(Websocket要點講解)

所以你可以在你的程式中判斷$frame->opcode的值,作出不同的操作,比如區別檔案的操作:

$server->on("message", function (Server $server, Frame $frame) {
    //echo $frame->data."-".$frame->opcode."\n";
    if ($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_BINARY) {
        echo "binary data received\n";
        file_put_contents("./beauty-received.jpg", $frame->data);
        $server->send($frame->fd, swoole_websocket_server::pack("received"));
    }
});

下圖是Swoole的官方文件截圖:

Swoole系列之二(Websocket要點講解)

Swoole除了設定以上的回撥之外,還可以設定onRequest回撥,注意了,這個非常重要,我們看官方點的描述:

設定了 onRequest 回撥,WebSocket\Server 也可以同時作為 http 伺服器
未設定 onRequest 回撥,WebSocket\Server 收到 http 請求後會返回 http 400 錯誤頁面
如果想通過接收 http 觸發所有 websocket 的推送,需要注意作用域的問題,程式導向請使用 global 對 WebSocket\Server 進行引用,物件導向可以把 WebSocket\Server 設定成一個成員屬性

上面的意思就是,如果你設定了onRequest回撥的話,那麼當前的Websocket伺服器也可以作為一個Http伺服器,比如:

$server->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) {
    echo "new request\n";
    $response->status(200);
    $response->write("hello");
});

你從瀏覽器訪問就可以看到結果。

上面說的onRequest不是我要說的重點,重點是作為Http請求的套接字被關閉時,close回撥也是會被呼叫的,大家切記

還有,$server->connections可以獲取到當前伺服器的所有套接字,但是你如何判斷連線的對端是websocket還是http(因為當前伺服器同時也是一個Http伺服器)呢?Swoole官方提供了一個方法可以解決這個問題:

$server->isEstablished($fd)

Swoole的push方法用於向客戶端推送資料,我們看它的簽名:

Swoole\WebSocket\Server->push(int $fd, string $data, int $opcode = 1, bool $finish = true): bool;

我要說的是第3,4個引數,第三個引數你需要設定當前傳送資料到底是二進位制資料還是文字資料,第四個參數列示當前傳送的資料是不是完整的資料,這個是啥意思呢?因為我們傳送的資料有可能非常大,比如一張圖片,有4M的大小,但是我們看官方的描述:

Swoole系列之二(Websocket要點講解)

所以我們一次傳送的資料是有限制的,此時就需要多次傳送,這就需要告訴客戶端,我還有資料沒法給你,不要急!你只需要設定第四個引數為true即可。

總結

上面是我覺得有必要說明的一些地方,當然還有其他的,Swoole交流群:

本作品採用《CC 協議》,轉載必須註明作者和本文連結

如果有不懂的地方,可以加我的qq:1174332406,或者是微信:itshardjs

相關文章