通常のphpやhtmlを使ったwebアプリケーションではリアルタイムに双方向通信をするのが難しい。

WebSocketという規格を使えばこの課題を解決でき、PHP上で動くRatchetというライブラリがあったので備忘録を残す

Composerをインストールする

composerはphpの外部ライブラリを管理するアプリケーション。
これによりRatchetの動作に必要なReact, Guzzle, HttpFoundationを解決してくれる。

任意のフォルダーで以下を実行

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php
php -r "unlink('composer-setup.php');"

phpの実行ファイルであるcomposer.pharが生成されていればOK

Ratchetをインストールする

前項を実行したフォルダーで以下を実行。ファイル群が生成される。

php composer.phar require cboden/ratchet

以降Ratchetを使いたいファイルにvendor/autoload.phpを読み込むように設定すればよい。

require 'vendor/autoload.php';

フォルダを分けたい場合は以下のマジカル定数などを利用し適宜変更

require __DIR__ . '/vendor/autoload.php';

MyChatクラスを作成する

同フォルダーに送信、受信、切断、接続があったときの動作を制定するクラスを作成する。ここでのファイル名はMyChat.phpとする

<?php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class MyChat 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();
    }
}

webサーバーロジックを作成する

これを常時起動させることでクライアントがメッセージをやり取りできるようになる。ファイル名は何でも良いがwebserver.phpとする。

<?php
// Your shell script
use MyApp\MyChat;

use Ratchet\WebSocket\WsServer;
use Ratchet\Http\HttpServer;
use Ratchet\Server\IoServer;

    require 'vendor/autoload.php';
    $ws = new WsServer(new MyChat);

    // Make sure you're running this as root
    $server = IoServer::factory(new HttpServer($ws),8080);
    $server->run();

依存関係を設定

同フォルダのcomposer.jsonに追記する。

MyChat.phpの名前空間MyAppがカレントフォルダにあることを設定している。適宜変更

{
    "autoload": {
        "psr-4": {
            "MyApp\\": "./"
        }
    }, 
    "require": {
        "cboden/ratchet": "^0.4"
    }
}

書き込みが終了したら忘れずに

php composer.phar update

ターミナルから以下を実行し、何も応答がなければ成功。

php webserver.php

フロントエンドの作成

JavaScriptでフロントエンド側の処理を記述する。localhostにMyChat.htmlなどと保存する。

<script>
var conn = new WebSocket('ws://localhost:8080');
conn.onopen = function (e) {
    console.log("connection for comment established!");
};
conn.onmessage = function (e) {
    console.log(e.data);
};
function send(text) {
    conn.send(text);
}
</script>

動作確認

Chromeを開き右クリック→「検証」
URLバーに該当するアドレス(localhost/MyChat.htmlなど)を挿入する

コンソールからsend(“text”);などとしてやると送信され、phpのwebserverが起動しているターミナルから確認できる。

webserver.phpは常時起動していないと行けないので、nohupを使うのが良いと思われ

nohup php webserver.php & >> /dev/null/