Mach3.laBlog

“WebSocket” – Alphabetical Advent Calendar 2013

この記事は賞味期限切れです。(更新から1年が経過しています)

“W” は WebSocket の “W”。

W

WebSocket

WebSocket は、サーバーとクライアント間の双方向の通信をスムーズに行う為の技術です。 XMLHttpRequest のように都度リクエストを送るのではなく、 一旦コネクションが貼られた後はそのコネクション上でメッセージを送り合ってデータのやり取りを行う為、 よりリアルタイム性が必要なアプリケーション、例えばチャットやオンラインゲーム等での活躍が期待されます。

Socket.io

WebSocket を恐ろしく簡単で使いやすい物に変えてしまうのが Socket.io です。

http://socket.io/
Socket.IO aims to make realtime apps possible in every browser and mobile device, blurring the differences between the different transport mechanisms. It’s care-free realtime 100% in JavaScript.

node.js で楽にWebSocketのサーバが立ち上がるうえ、クライアントサイドでは WebSocket に非対応な環境の為に、 Flash で ActionScript3 の WebSocket API を使用してフォールバックを備えてくれています。

WebSocket サーバの起動

まず socket.io をインストールしましょう。

$ npm install socket.io

そして起動用のスクリプトを書きます。

// server.js
var io = require('socket.io').listen(8080);
io.sockets.on("connection", function(socket){
    socket.emit("hello", {message: "Thanks to connect !"});
});

これは接続したクライアントに “hello” メッセージを送るだけのコードです。

listen メソッドでポートを指定してサーバを起動します。 io.sockets.on メソッドでは、新たにリクエストのあったコネクションを受け取り、 そのコールバックの中で emit コマンドでクライアント側にメッセージを送ります。

node コマンドで WebSocket サーバを起動します。

$ node server.js

これでサーバが立ち上がったと思います。 デーモン化したい時には forever が便利です。

クライアント側の処理

WebSocketとは別に、Webサーバが立ち上がっていると仮定しましょう。 クライアント側では socket.io-client を使って WebSocket に接続します。 socket.io-client は bower でも npm でもインストールできます。

$ bower install socket.io-client

ここではサーバから送った “hello” メッセージを受け取ってみます。

<script src="bower_components/socket.io-client/dist/socket.io.js"></script>
<script>
    var socket = io.connect("http://example.com:8080");
    socket.on("hello", function(data){
        console.log(data.message); // "Thanks to connect !"
    });
</script>

サーバから emit で送ったメッセージは、クライアント側で on で受け取る事ができます。 逆に、サーバ側にメッセージを送る時はクライアント側で emit し、サーバ側で on で受け取ります。

全てのソケットにメッセージを送る

上のサーバスクリプトでは、引数で渡された socket オブジェクトで emit を行っていましたが、 この socket はそのクライアントのみのコネクションです。 接続している全てのユーザにメッセージを送りたい場合は、socket にぶら下がっている broadcast を使用します。

socket.emit("message", ... ); // 自分にメッセージを送る
socket.broadcast.emit("message", ...); // 自分以外にメッセージを送る

broadcast は自分以外のコネクションを指します。

極めてシンプルなチャットの習作

Socket.io を使って極めてシンプルなチャットプログラムを作ってみます。 上で挙げた機能のみを使用して、その場限りのチャットをする事が出来ます。 ルームや認証などは一切省いています。

まずHTMLは、ニックネームとメッセージを入力するインプットと、メッセージログを出力する #log で構成されます。

<!-- HTML -->
<form id="form-message">
    <label>ニックネーム: <input type="text" id="nickname"></label>
    <label>メッセージ: <input type="text" id="message"></label>
    <input type="submit">
</form>
<hr>
<ul id="log"></ul>

次にクライアント側の JavaScript ですが、例をシンプルにする為に jQuery を使用します。

/* クライアント JavaScript */
var socket = io.connect("http://example.com:8080");

socket.on("connect", function(){
    $("#nickname").on("change", function(e){
        socket.emit("set nickname", {
            nickname: this.value
        });
    });
    $("#form-message").on("submit", function(e){
        e.preventDefault();
        var input = $("#message");
        socket.emit("send message", {
            message: input.val()
        });
        input.val("");
    });
});

socket.on("message", function(data){
    $("<li>").append($("<strong>").text(data.nickname))
    .append($("<span>").text(data.message))
    .prependTo("#log");
});

クライアントサイドの JavaScript はこのような働きをします。

  • #nickname の内容が変更されると “set nickname” メッセージを送ってニックネームを変更する
  • #form-message がサブミットされると、#message から “send message” でメッセージを送信する
  • サーバから “message” が届いたら、 #log の中に追加して表示する

最後にサーバ側では、上記の処理をサポートするように記述します。

/* サーバ JavaScript */
var io = require('socket.io').listen(8080);

io.sockets.on("connection", function(socket){
    var anonymous = "名無しさん";
    var nickname = null;
    socket.on("set nickname", function(data){
        nickname = data.nickname;
    });
    socket.on("send message", function(data){
        if(! data.message){ return; }
        var vars = {
            nickname: nickname || anonymous,
            message: data.message
        };
        socket.emit("message", vars);
        socket.broadcast.emit("message", vars);
    });
});

socket オブジェクトには set/get が実装されており、その接続毎のデータを保持する事ができますが、 コールバックを受け取るタイプで少し例が冗長になってしまう為、ここでは単なる変数として nickname を定義しています。

WebSocket の働きが、なんとなく伝わったでしょうか。

その他のヒント

限定されたユーザにメッセージを送りたい

例えばチャットルームのような物が存在していて、その中だけでメッセージをやりとりしたい時の為に、 Socket.io には 「ルーム機能」 なる物があります。 ルームは入室・退室する事ができ、そのルームの中にいるメンバーだけに通知をする事ができます。

/*  サーバ JavaScript */
socket.join("ChatRoom"); // 入室します
socket.leave("ChatRoom"); // 退室します
io.sockets.in("ChatRoom").emit( ... ); // ルーム内のメンバーにだけ emit

ルーム内にメッセージを送るには、io.socketsin() メソッドで絞り込んで emit します。

また、コネクションからクライアントを切り分ける ネームスペース機能 という物もあります。 “/chat” のようにネームスペースを定義してそこに接続したクライアントだけでデータのやり取りをします。 サーバ側では of() でクライアントを絞り込んで処理をかき分けます。

/*  サーバ JavaScript */
var chat = io.sockets.of("/chat");
chat.on("connection", function(socket){
    socket.on("send message", function(data){
        chat.emit( ... );
    });
});

クライアント側では、コネクション時に “/chat” を足して接続します。

/* クライアント JavaScript */
var socket = io.connect("http://example.com:8080/chat");

また、ネームスペースとルーム両方で絞り込む事もできます。

セッションを管理したい

socket.io のセッションは基本的にその接続限りです。 ページをリロードしたり、新しいタブで開いたりした時点で新しいコネクションが貼り直され、id も更新されてしまいます。 その為、認証などでセッションの保持をしたい場合は少し工夫が必要です。

socket.io の wiki にいくつかその為のレシピやモジュールが公開されていますが、 基本的に expressconnect など、node.js ベースの解決策になっているので、 socket.io を採用する場合はWebサーバも express を選択した方が良さそうですね。 (特に他の物を使う理由もなさそうですが)

session.socket.io (SessionSockets)
This tiny node module simplifies your web sockets app when using http sessions from express or connect middlewares. It has no dependencies and can be initialized using any session store and cookie parser compatible with express or connect.

こちらは socket.io でセッションを管理する為の node モジュールです。

参考資料

コメント

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*