讓swoole websocket支持socket.io連接

讓swoole websocket支持socket.io連接

1 人贊了文章

最近接觸了socket.io,讓我產生了一個疑問,既然socket.io支持使用websocket協議通信,那麼,可不可以讓socket.io客戶端,和websocket服務端進行通信呢?

答案當然是可以的。

首先,客戶端在連接時,指定傳輸方式為: 僅使用websocket

var socket = io({transports: [websocket]});

這樣服務端就可以收到客戶端發來的數據了,但是,該怎麼解析這些數據呢?

先來列印一下最常見的socket.io包吧:

SwooleWebSocketFrame Object( [fd] => 1 [data] => 42["test",{"hello":"there"}] [opcode] => 1 [finish] => 1)

請看[data]欄位,是由一個數字和一個json組成的字元串, 隨便百度一下socket.io的數據包,貌似都是這樣的結構:數字+json, json很好理解——就是發送的內容,那麼這個數字到底是什麼意思呢?

那就要看socket.io的文檔或者源碼了。經過一番[學習],我大概知道了這個數字是怎麼回事了。

這個數字從左開始的第一位,可能是以下數字的一種,代表包的類型:

0 open —— Sent from the server when a new transport is opened (recheck)1 close —— Request the close of this transport but does not shutdown the connection itself.2 ping —— Sent by the client. Server should answer with a pong packet containing the same data.3 pong —— Sent by the server to respond to ping packets.4 message —— actual message, client and server should call their callbacks with the data.5 upgrade —— Before engine.io switches a transport, it tests, if server and client can communicate over this transport. If this test succeed, the client sends an upgrade packets which requests the server to flush its cache on the old transport and switch to the new transport.6 noop —— A noop packet. Used primarily to force a poll cycle when an incoming websocket connection is received.

2是客戶端心跳ping,3是服務端心跳pong,4是真正的消息。

這個數字從左開始的第二位,可能是以下數字的一種,代表包的消息的類型:

0 Packet#CONNECT 1 Packet#DISCONNECT 2 Packet#EVENT //事件 3 Packet#ACK //事件確認 4 Packet#ERROR 5 Packet#BINARY_EVENT 6 Packet#BINARY_ACK

我們最常見的socket.io數據包,就是以數字42為前綴的(消息事件),比如我一開始貼出的代碼 —— 42["test",{"hello":"there"}]。

有時候,前綴數字的長度超過了兩位,比如420,421,....,4299,....

其實這也是一個消息事件,表示接收方收到之後,需要確認這個事件,也即是說:

發送方發送了420這個包,接收方必須回一個430,

發送方發送了421這個包,接收方必須回一個431,

發送方發送了4299這個包,接收方必須回一個4399 ....

梳理一下,socket.io的數據包,基本是這種格式:

包類型+消息類型+確認ID+json //json可以沒有,確認ID可以沒有,消息類型也可以沒有

知道了基本原理,就讓我們在websocket伺服器上, 來封裝一個socket.io解析器吧

(簡單實現socket.io客戶端和websocket服務端通信)

在websocket服務上,我想要實現類似socket.io的處理方式,像這樣:

<?php$swoole = new swoole_websocket_server(0.0.0.0, 3001);$socketioHandler = SocketIOParser::getInstance();//register socketio events$socketioHandler->on(connection, function ($socket) { echo connection:.$socket->id.PHP_EOL;});$socketioHandler->on(disconnect, function ($socket) { echo disconnected:.$socket->id.PHP_EOL;});$socketioHandler->on(message, function ($socket, $data) { echo message:.PHP_EOL; print_r($data); $socket->emit(message, [hello => message received]); $socket->disconnect();});$socketioHandler->on(message_with_callback, function ($socket, $data, $ack = ) { echo message_with_callback:.PHP_EOL; print_r($data); $ack && $ack(hello there);});//start websocket server$socketioHandler->bindEngine($swoole);$swoole->start();

現在, 我需要一個SocketIOParser類,可以用它來模擬socket.io事件註冊,並解析socket.io數據包:

<?php//由於不想讓文章太臃腫,沒有貼出所有的代碼,只貼出了主要流程,源碼請查看文末的鏈接,見諒class SocketIOParser{ //綁定swoole的 onMessage public function bindEngine($server) { $server->on(Message, [$this, onMessage]); } //模擬socket.io觸發事件 public function on($event, Closure $callback) { if (is_string($event)) { $this->events[$event] = $callback; } } //服務端解析消息, 有點簡單粗暴了 public function onMessage($server, $frame) { $this->server = $server; $this->id = $frame->fd; if ($index = strpos($frame->data, [)) { $code = substr($frame->data, 0, $index); $data = json_decode(substr($frame->data, $index), true); } else { $code = $frame->data; $data = ; } switch (mb_strlen($code)) { case 0:break; case 1: switch ($code) { case 2: //client ping $server->push($frame->fd, 3); //sever pong break; } break; case 2: switch ($code) { case 41: //client disconnect $this->close(); break; case 42: //client message if (isset($this->events[$data[0]])) { $this->events[$data[0]]($this, $data[1]); } break; } break; default: switch ($code[0]) { case 4: //client message switch ($code[1]) { case 2: //client message with ack $this->ackId = substr($code, 2); $this->events[$data[0]]($this, $data[1], [$this, ack]); break; case 3: //client reply to message with ack break; } break; } break; } } //發送數據到客戶端 public function emit($event, $data) { return $this->server->push($this->id, 42.json_encode([$event, $data])); }}

當然,socket.io有非常多的特性,比如支持輪詢,房間分組模式,更多內容請參考官網文檔

但是,如果你僅僅是想使用socket.io的websocket通信方式,你也可以像我一樣,在websocket服務上,封裝一個socket.io解析器,來實現更多的自由發揮。

按照慣例,貼上我的源碼(很小,一百多行代碼):

在swoole websocket服務上,實現socket.io通信

既是簡單的php websocket客戶端,也是php socket.io客戶端

(最後,內心默默的感謝一下我的朋友小張的大力支持,各位大佬請忽略此條內容)


推薦閱讀:

C# 完成WebSocket demo
Spring boot+web Socket即時通訊
WebSocket客戶端連接不上和掉線的問題以及解決方案
全雙工通信的 WebSocket

TAG:WebSocket | socketio | Swoole |