1. 程式人生 > 實用技巧 >Swoole從入門到入土(16)——WebSocket伺服器[事件]

Swoole從入門到入土(16)——WebSocket伺服器[事件]

WIKI:

問:websocket協議雖然和http協議不同,但是兼容於http協議,如何判斷客戶端連線使用的是http協議?

答:通過使用 $server->connection_info($fd) 獲取連線資訊,返回的陣列中有一項為 websocket_status,根據此狀態可以判斷是否為 WebSocket 客戶端。

---------- 正文的分割線 -------------

Swoole\WebSocket\Server繼承自Swoole\Http\Server,所以websocket server支援http server和tcp server的所有事件。另外,新增加了以下3個事件:

· onMessage (必選)

· onOpen 和 onHandShake (可選)

事件詳解

onHandShake:WebSocket 建立連線後進行握手。WebSocket 伺服器會自動進行 handshake 握手的過程,如果使用者希望自己進行握手處理,可以設定 onHandShake 事件回撥函式。

onHandShake(Swoole\Http\Request $request, Swoole\Http\Response $response);

· onHandShake 事件回撥是可選的,需要自行處理 handshake 的時候,再設定這個回撥函式。如果您不需要 “自定義” 握手過程,那麼不要設定該回調,用 Swoole 預設的握手即可。
· 設定 onHandShake 回撥函式後不會再觸發 onOpen 事件,需要應用程式碼自行處理
· onHandShake 中必須呼叫 response->status() 設定狀態碼為 101 並呼叫 response->end() 響應,否則會握手失敗.
· 內建的握手協議為 Sec-WebSocket-Version: 13,低版本瀏覽器需要自行實現握手
· 可以使用 server->defer 呼叫 onOpen 邏輯

示例:

$server->on('handshake', function (\Swoole\Http\Request $request, \Swoole\Http\Response $response) {
        // print_r( $request->header );
        // if (如果不滿足我某些自定義的需求條件,那麼返回end輸出,返回false,握手失敗) {
        //    $response->end();
        //     return false;
        // }

        // websocket握手連線演算法驗證
$secWebSocketKey = $request->header['sec-websocket-key']; $patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#'; if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) { $response->end(); return false; } echo $request->header['sec-websocket-key']; $key = base64_encode( sha1( $request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true ) ); $headers = [ 'Upgrade' => 'websocket', 'Connection' => 'Upgrade', 'Sec-WebSocket-Accept' => $key, 'Sec-WebSocket-Version' => '13', ]; // WebSocket connection to 'ws://127.0.0.1:9502/' // failed: Error during WebSocket handshake: // Response must not include 'Sec-WebSocket-Protocol' header if not present in request: websocket if (isset($request->header['sec-websocket-protocol'])) { $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol']; } foreach ($headers as $key => $val) { $response->header($key, $val); } $response->status(101); $response->end(); });

onOpen:當 WebSocket 客戶端與伺服器建立連線並完成握手後會回撥此函式。

onOpen(Swoole\WebSocket\Server $server, Swoole\Http\Request $request);

· $request 是一個 HTTP 請求物件,包含了客戶端發來的握手請求資訊
· onOpen 事件函式中可以呼叫 push 向客戶端傳送資料或者呼叫 close 關閉連線
· onOpen 事件回撥是可選的

onMessage:當伺服器收到來自客戶端的資料幀時會回撥此函式。

onMessage(Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame)

· $frame 是 Swoole\WebSocket\Frame 物件,包含了客戶端發來的資料幀資訊
· onMessage 回撥必須被設定,未設定伺服器將無法啟動
· 客戶端傳送的 ping 幀不會觸發 onMessage,底層會自動回覆 ping 包,也可設定 open_websocket_ping_frame 引數手動處理

關於Swoole\WebSocket\Frame $frame

·$frame->data 如果是文字型別,編碼格式必然是 UTF-8,這是 WebSocket 協議規定的

示例:

面向過程寫法:

$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->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) {
    global $server;//呼叫外部的server
    // $server->connections 遍歷所有websocket連線使用者的fd,給所有使用者推送
    foreach ($server->connections as $fd) {
        // 需要先判斷是否是正確的websocket連線,否則有可能會push失敗
        if ($server->isEstablished($fd)) {
            $server->push($fd, $request->get['message']);
        }
    }
});
$server->start();

面向物件寫法:

class WebSocketTest
{
    public $server;

    public function __construct()
    {
        $this->server = new Swoole\WebSocket\Server("0.0.0.0", 9501);
        $this->server->on('open', function (Swoole\WebSocket\Server $server, $request) {
            echo "server: handshake success with fd{$request->fd}\n";
        });
        $this->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");
        });
        $this->server->on('close', function ($ser, $fd) {
            echo "client {$fd} closed\n";
        });
        $this->server->on('request', function ($request, $response) {
            // 接收http請求從get獲取message引數的值,給使用者推送
            // $this->server->connections 遍歷所有websocket連線使用者的fd,給所有使用者推送
            foreach ($this->server->connections as $fd) {
                // 需要先判斷是否是正確的websocket連線,否則有可能會push失敗
                if ($this->server->isEstablished($fd)) {
                    $this->server->push($fd, $request->get['message']);
                }
            }
        });
        $this->server->start();
    }
}

new WebSocketTest();

--------------------------- 我是可愛的分割線 ----------------------------

最後博主借地宣傳一下,漳州程式設計小組招新了,這是一個面向漳州青少年資訊學/軟體設計的學習小組,有意向的同學點選連結,聯絡我吧。