php – WebSocket 架設

安裝套件
我們使用 Ratchet 這款 PHP WebSocket 架設。使用這款套件是不需要安裝其他元件,安裝執行比較簡單,但我後來推薦使用 Swoole 這個效能很好的 PHP 擴展,Swoole 在一開始安裝上稍微麻煩一點,但在後續使用上會簡單許多。以下介紹 Ratchet 的安裝方式:
我們使用 composer.json 安裝,先建立 /composer.json
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "autoload": { "psr-0": { "MyApp": "src" } }, "require": { "cboden/ratchet": "0.3.*" } } |
雖然官方範例使用 psr-0 較舊的規範,不過不會影響我們的範例。接著運行指令下載
1 2 3 |
composer install |
建立應用程式 (Application)
在 /src/MyApp/Chat.php 建立這個官方範例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<?php namespace MyApp; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; class Chat implements MessageComponentInterface { protected $clients; public function __construct() { $this->clients = new \SplObjectStorage; } public function onOpen(ConnectionInterface $conn) { // Store the new connection to send messages to later $this->clients->attach($conn); echo "New connection! ({$conn->resourceId})\n"; } public function onMessage(ConnectionInterface $from, $msg) { $numRecv = count($this->clients) - 1; echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n" , $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's'); foreach ($this->clients as $client) { if ($from !== $client) { // The sender is not the receiver, send to each client connected $client->send($msg); } } } public function onClose(ConnectionInterface $conn) { // The connection is closed, remove it, as we can no longer send it messages $this->clients->detach($conn); echo "Connection {$conn->resourceId} has disconnected\n"; } public function onError(ConnectionInterface $conn, \Exception $e) { echo "An error has occurred: {$e->getMessage()}\n"; $conn->close(); } } |
結構上會是這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?php namespace MyApp; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; class Chat implements MessageComponentInterface { // 當有新的客戶端連線時 public function onOpen(ConnectionInterface $conn) { } // 當 Connection 接收到訊息 public function onMessage(ConnectionInterface $from, $msg) { } // 當 Connection 關閉連線時 public function onClose(ConnectionInterface $conn) { } // 當 Connection 發生錯誤時 public function onError(ConnectionInterface $conn, \Exception $e) { } } |
實體化
Chat.php 是我們應用程式的類別,我們需要將它實體化運行。在 /bin/chat-server.php 加入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use MyApp\Chat; require dirname(__DIR__) . '/vendor/autoload.php'; $server = IoServer::factory( new HttpServer( new WsServer( new Chat() ) ), 8080 ); $server->run(); |
我們建立個一個輸入/輸出的服務類別,並告訴伺服器運行的事件要不斷循環,並監聽 Port 8080 的任何請求。我們打開 terminal ,來到跟目錄底下運行
1 2 3 |
php bin/chat-server.php |
這就我們的程式已經開始執行監聽了。
開始溝通
首先,我們建立 2 個 HTML 來示範。/index.html 與 /send.html。
index.html
1 2 3 4 5 6 7 8 9 10 |
var conn = new WebSocket('ws://localhost:8080'); conn.onopen = function(e) { conn.send('hello from index.html'); console.log("Connection established!"); }; conn.onmessage = function(e) { console.log(e.data); }; |
打開網址 http://localhost/index.html 在 Console 的地方會看到連線已經建立
1 2 3 |
Connection established! |
然後可以看看 terminal 出現了新連線的提示、還有接收到什麼訊息
1 2 3 4 5 |
C:\xampp\htdocs\www\websocket\server>php bin/chat-server.php New connection! (45) Connection 45 sending message "hello from index.html" to 0 other connections |
接著 send.html 寫入
1 2 3 4 5 6 7 8 9 10 |
var conn = new WebSocket('ws://localhost:8080'); conn.onopen = function(e) { conn.send('hello from send.html'); console.log("Connection established!"); }; conn.onmessage = function(e) { console.log(e.data); }; |
打開網址 http://localhost/send.html 在 Console 的地方會一樣會看到連線已經建立。
1 2 3 |
Connection established! |
回到 index.html 會發現 Console 出現接收到的字串
1 2 3 4 |
Connection established! hello from send.html |
不論重新整理 index.html 或 send.html 都能再另外一方接收到傳遞訊息。我們可以看到連線狀態都保持 websocket 的連線。