Version 0.4.3
— y2sunlight 2020-11-14
関連記事
リンク
本章は、Ratchet 公式サイトのチュートリアル(Hello World!)のレビューです。実際にチュートリアルを行った結果を補足とともに説明します。ここでは、PSR-4 と Composer に精通していることを前提としています。それらについては本編でも説明しているので、そちらをご覧ください。
まず、プロジェクトフォルダ(以下 {Project-Folder}
と呼びます)を作成し、そこにComposerファイルを設置します。
{ "autoload": { "psr-4": { "MyApp\\": "src" } }, "require": { "cboden/ratchet": "^0.4" } }
上の設定では、プロジェクトフォルダ下の src
フォルダにアプリケーションを設置し、そこを MyApp
名前空間として定義します。
Composer の規則に従い、ratchet は vender/cboden/ratchet
下に保存されます。また、チャットサーバ起動用のスクリプトを配置する bin
フォルダを作ります。
プロジェクトフォルダ下に以下のフォルダを作成します:
src
— アプリケーションソースを格納しますbin
— アプリケーション起動用のスクリプトを格納します
準備が出来たら、コマンドプロンプトでプロジェクトフォルダに移り、composer install
を実行します。
> composer install Loading composer repositories with package information Warning from https://repo.packagist.org: You are using an outdated version of Composer. Composer 2.0 is now available and you should upgrade. See https://getcomposer.org/2 Updating dependencies (including require-dev) Package operations: 18 installs, 0 updates, 0 removals - Installing symfony/polyfill-php80 (v1.20.0): Downloading (100%) - Installing symfony/deprecation-contracts (v2.2.0): Downloading (100%) ... Writing lock file Generating autoload files
チュートリアルでは、簡単なチャットアプリケーションを作成します。それは、すべての着信メッセージを受け入れ、そのメッセージを他のすべての接続に配信するだけのものです。送信メッセージは、お決まりの 「HelloWorld!」 にしましょう。
まず、アプリケーション本体になる Chat
クラスをsrcフォルダの下に作成します。
{Project-Folder}/src/Chat.php
<?php namespace MyApp; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; class Chat implements MessageComponentInterface { public function onOpen(ConnectionInterface $conn) { } public function onMessage(ConnectionInterface $from, $msg) { } public function onClose(ConnectionInterface $conn) { } public function onError(ConnectionInterface $conn, \Exception $e) { } }
このクラスは、MessageComponentInterface
を実装し、次の4つのイベントハンドラを作ります。
こられのイベントハンドラは、ConnectionInterface
オブジェクトを引数に持っています。それは、ソケットの反対側のクライアントの接続を表しています。
Chat
クラスは、実際のアプリケーションロジックを含みますが、これをインスタンス化するスクリプト ( チャットサーバと呼ぶ事にします ) を作ります。このスクリプトは、コマンドラインから呼び出されます。
{Project-Folder}/bin/chat-server.php
<?php use Ratchet\Server\IoServer; use MyApp\Chat; require dirname(__DIR__) . '/vendor/autoload.php'; $server = IoServer::factory( new Chat(), 8080 ); $server->run();
ここでは、IoServer を作成しています。
IoServer
はチャットアプリケーションのベースとなるイベント処理を行う中核のサーバクラスです。IoServer
クラスは、確立されたすべての接続を保存し、各クライアントとチャットアプリケーション間で送信されるデータを仲介します。また、エラーがあればそれをキャッチします。
ここでは、Chatクラスの新しいインスタンスが IoServer
をラップし、ポート8080で着信要求をリッスンしてイベントループに入るようにサーバーに指示します。
コマンドプロンプトを起動しプロジェクトフォルダに移動し、bin/chat-server.php
を起動します:
php bin/chat-server.php
次に、別のコマンドコマンドプロンプトを起動し以下のコマンドを実行します。
netstat -an | find "8080"
以下のようにリスニングポートが開いていたらOKです。
TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING
まだプログラムは完成していないのでこれ以上は何も起こりません。Ctrl + c
でスクリプトを停止して下さい。
先に作成した Chat クラスのスケルトンにロジックを追加します。
メッセージを送信するために着信の接続を追跡し、ここでは SplObjectStorage と呼ばれるコンテナに格納します。このストレージコンテナは、オブジェクトを格納するように構築されています。
SplObjectStorage は、PHP 5.2 以降に搭載された便利なコンテナクラスです。
{Project-Folder}/src/Chat.php
<?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(); } }
SplObjectStorage
オブジェクトを作成しますSplObjectStorage
オブジェクトに格納しますSplObjectStorage
オブジェクトからクローズされた接続を除外します
非常にシンプルなロジックですが、Ratchet の MessageComponentInterface
を理解するには良い例題です。
アプリケーションが完成したので、実行してみましょう。
まず、コマンドプロンプトでチャットサーバ (chat-server.php
) を起動します。
php bin/chat-server.php
別のコマンドプロンプトから以下のようにTelnetを起動します。
Telnet が有効になっていない場合は、こちらをご覧下さい。
telnet localhost 8080
この時、チャットサーバから以下のメッセージが出力されます。
New connection! (37)
さらに、別のコマンドプロンプトからもTelnetを起動します。
telnet localhost 8080
上と同じように、チャットサーバから2番目のメッセージが出力されます。
New connection! (37) New connection! (48)
Telnetを起動した一方のコマンドプロンプトから、メッセージ( Hello World!
)を入力すると、他方のウィンドウにメッセージが表示されます。尚、Telnetを終了するには、ctrl + ]
でコマンドモードに移行し、quit
コマンドを入力します。
ここまではTCPレベルのアプローチです。主な機能は MessageComponentInterface に集約されており、イベントハンドリングが主体で、WebSocketAPI はまだ機能していません。
基本的なチャットアプリケーションが機能するようになったので、今度はWebブラウザでそれが機能するようにチャットサーバを変更します。
{Project-Folder}/bin/chat-server-ws.php
<?php use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use MyApp\Chat; require dirname(__DIR__) . '/vendor/autoload.php'; $ws = new WsServer(new MyChat); $server = IoServer::factory( new HttpServer($ws), 8080 ); $server->run();
WsServer を使用すると W3C WebSocketAPI を使用するWebブラウザーと通信できるようになります。HttpServer は、着信HTTPリクエストの解析を担当します。このクラスの目的は、完全なHTTPヘッダー要求を受信して渡すまでデータをバッファリングすることで、 WebSocket リクエストをアップグレードするためのものです。
このチャットをテストするために、次のファイルを作ります。(HTMLファイルでも問題ありません)
{Project-Folder}/src/test-ws.php
<script type="text/javascript"> var conn = new WebSocket('ws://localhost:8080'); conn.onopen = function(e) { console.log("Connection established!"); }; conn.onmessage = function(e) { console.log(e.data); }; </script>
コマンドプロンプトでチャットサーバ(chat-server-ws.php
)を実行します。
php bin/chat-server-ws.php
次に、2つのブラウザウィンドウを開いて test-ws.php
にアクセスし、ブラウザの開発ツールを開いて下さい。そして、デバックコンソールに次のメッセージが出力されていることを確認して下さい。
Connection established!
このメッセージが出ていたら、一方のブラウザのデバックコンソールから次のコードを実行します:
conn.send('Hello World!');
この時、他方のデバックコンソールに “Hello World!” が表示されます。
これで Ratchet のチュートリアル「Hello World!」を終わります。