Ajax
の登場で、非同期通信が当たり前なって、Webアプリではクライアントからサーバーへの一般的な通信手段になっている。一方Node.jsの学習していると、サーバーとの通信手段としてSocket.IO
に関する記述が多く見られる。
いったいSocket.IOがどんなものか、基本的なことから学習したメモを残す。
まず、Socket.IOの特徴について簡単にまとめると、
- TCP上で動作する双方向通信規格
WebSocket
に準拠したNode.js用ライブラリであり、その他のCGI系言語もサポートされている。 - サーバーからクライアントへの
プッシュ配信
が可能(もちろんクライアントからサーバへも) 非同期
で、応答などはすべて、イベント駆動
である
Socket.IOのインストール
Node.jsの他のモジュールと同じくnpmコマンドでインストールする。
※2015-09-16時点、最新Node.js(node : v4.0.0, npm : 2.14.2)で、ver1.3.6のNode.jsをインストールすると、プラットフォームに合わせて、ビルドが実行されるが、依存モジュールである、utf-8-validate
とbufferutil
のビルドで失敗する。
※2015/11時点の最新版では、正常にビルドされることを確認
アプリケーション構成
※下記のプログラムは、Node 4.0.0、express 4.13.3、socket.io 1.3.6で確認した内容である。(ただしビルドエラー対応を独自に実施している)アプリケーション構成は以下のとおり、実際にはほかにもあるが、関係あるファイルだけを記載。
sample
├ app.js Node.jsアプリのメインスクリプト(socket.ioのサーバ実装)
└ public
├ root.html クライアント画面(socket.ioのクライアント実装)
└ js
└ jq
└ jquery-1.9.1.js HTML処理にjQueryを使用
現実世界の例とSocket.IOの比較
現実世界において、なんらかの館、店などでは、一般的に以下のような感じである。
- “管理人”は、1つの建屋に対し一人である。
- その”管理人”は幾つかの”部屋”を管理することができる。
- “各部屋”には、何人もの人が”入室”して”会話”することができる。
- 同じ部屋であれば、特定の人とヒソヒソ話することもできれば、大声で”部屋の全員”に向かって話すこともできる。
- “入室した人”は、隣の部屋の話を聞くことはできないが、管理人は館内放送のように全員に向かってメッセージを投げることができる。
- 何らかの理由で、管理人は特定ユーザの入室を拒否することができる。
- 同一ユーザがいくつもの部屋を行き来して、各部屋の人と交流することもできる。
- 部屋にはたくさんの人がいて、いろんな”話題”でそれぞれ会話が成り立っている。
Socket.IOは上記をjavascript化したようなつくりになっている。
上記の例 | Socket.IO |
---|---|
管理人 | Socket.IOサーバ |
建屋 | Socket.IOモジュールまたはSockets |
部屋 | Socket |
人 | クライアントまたは接続Id |
入室 | Connect |
会話 | Emit |
特定の人 | Socket(Id) |
部屋の全員 | Socket.Broadcast |
館内放送 | Sockets.Emit |
話題 | On, Emitのイベント名 |
Socket.IOの大まかな使い方
基本的には、クライアントもサーバーも”双方向”だけあって実装方法が似ており、わりとシンプルである。
on
メソッドでメッセージを待つemit
メソッドでメッセージを送信する- 各メソッドは、
非同期
で実行されて、結果はコールバック
で受信する
基本的なやりとりは以上である。非同期かつコールバックによる送受信なので、コールバック内にコールバックが定義されていているようなjavascriptの良くあるスタイルになる。
ここでは、チャットプログラムを例として、サーバー側のシンプルな実装をまとめる。下例は、Socketサーバーがmessage
イベントを監視し、メッセージを受信したら、同じ部屋にいるユーザ全員にそのメッセージを配信するシンプルなプログラムになる。
サーバー側の実装
// Socketサーバー開始
var socketio = require('socket.io');
var io = socketio.listen(server);
// クライアントからの接続を検知すると、"connection"イベントのコールバックが発火する
// このfunctionには、接続が確立したsocketが引数として渡される。
io.sockets.on("connection", function(socket){
// この接続に対し、"message"イベントを監視するように設定
socket.on("message",function(data){
//受信したデータをほかのsocketユーザにブロードキャスト送信する
socket.broadcast.emit("message", data);
// 送信ユーザにも同じメッセージを返す(ブロードキャストには送信ユーザが含まれない)
socket.emit("message", data);
});
});
クライアント側の実装
/socket.io/socket.io.js
へのjavascriptファイルの参照を追加すると、javascriptのグローバルスコープにio
オブジェクトが追加されてアクセスできるようになる。(socket.io.jsというファイルは存在しない
)
io.connect()
で接続して取得したSocketオブジェクトに対し、on
メソッドで受信設定を行い、emit
でデータ送信する流れになる。サーバーとほとんど同じである。接続ユーザに対して与えられるユニークなIDはSocket.id
から取得できるが、接続完了するまでは、undefinedのままである。 ※下記はHTMLコードは省略している
<script src="http://localhost:3000/socket.io/socket.io.js"></script>
<script>
$(function(){
// socketサーバーに接続
var socket = io.connect("http://localhost:3000");
// 接続結果イベント
socket.on("connect", function(message){
// socketサーバーから発番されたユニークIDを表示する
$("#chat_message").append($('<li>').text("connected. id = "+socket.id));
});
// "message"イベント
// socketサーバーから"message"向けに配信したデータを受信する
socket.on("message",function(message){
$("#chat_message").append($('<li>').text(message));
});
// 送信ボタンをクリックしたら、入力ボックス
// の文字列を"message"イベントへ送信する
$("#send_btn").on("click", function(){
var msg = $("#input_message").val();
socket.emit("message", msg);
});
});
</script>
実際に実行すると以下のようになる。