前回では、Socket.IOの基本的な使い方に関してメモした。今回は、1歩踏み込んだ使い方に関するメモを残す。
Socket.IOのネームスペース
前回最後に掲載したサンプルコードでは、部屋
に関して特に意識したコードにはなってなかったが、Socket.IOではわりと簡単に部屋を分けることができる。下記例では、room1
とroom2
を作成してそれぞれの部屋でチャットできるようにする。
サーバー側の実装
var socketio = require('socket.io');
var io = socketio.listen(server);
//io.sockets.on("connection", function(socket){
io.of("/room1").on("conne ction", function(socket){
socket.on("message", function(data){
socket.emit("message", "[room1]" + data.value);
socket.broadcast.emit("message", "[room1]" + data.value);
});
});
io.of("/room2").on("connection", function(socket){
socket.on("message", function(data){
socket.emit("message", "[room2]" + data.value);
socket.broadcast.emit("message", "[room2]" + data.value);
});
});
io.socketsをio.of(name_space)に変更するだけ
で実現できる。
クライアント側の実装
var room1 = io.connect("http://localhost:3000/room1");
var room2 = io.connect("http://localhost:3000/room2");
// room1
room1.on("connect", function(message){
$("#chat_message1").append($('<li>').text("connected. id = "+room1.id));
});
room1.on("message", function(message){
$("#chat_message1").append($('</li><li>').text(message));
});
$("#send_btn1").on("click", function(){
var msg = $("#input_message1").val();
room1.emit("message", msg);
});
// room2
room2.on("connect", function(message){
$("#chat_message2").append($('</li><li>').text("connected. id = "+room2.id));
});
room2.on("message", function(message){
$("#chat_message2").append($('</li><li>').text(message));
});
$("#send_btn2").on("click", function(){
var msg = $("#input_message2").val();
room2.emit("message", msg);
});
クライアント側は、io.connect()で指定するURLを[サーバー名]+[部屋名]
にして、
それぞれのソケット(room1, room2)に対し、on
とemit
を実装するだけである。
下図は前回のチャットサンプルを改造して、左右同じチャットプログラムを作成してネームスペースをroom1、room2に分けたものである。
- 接続直後の状態。セッションが同じなので、左右のネームスペース共にIDが同じである。
- 左側のroom1に対し、メッセージを送信すると、相手側のroom1だけにメッセージ送信される。
- こんどは、相手側のroom2からメッセージを送信すると、右側だけにメッセージが送信される。
1つのソケットに対し、イベント名を複数定義すれば、似たようなことはできるが、socket.broadcast()の結果が違ってくる。http通信(ajax)の置き換えとして使うのであれば、クライアントとサーバーは1対1なので、イベント名を複数用意するほうがお手軽じゃないかと。1対Nなら、ソケットを複数用意した使いかたを検討すれば良さそう。
Socket.IOのユーザ認証
グローバル認証とネームスペース認証の2つがある。
※socket.ioのver1.0
以降では、実装方法が変更になった模様.以下はsocket.io v1.3.6
を使用している.
サーバー側では、io.use(callback(socket,next))
メソッドを定義する。接続してきたクライアントごとにコールバックが発火して、接続情報がcallbackのsocket引数に渡される
この内容を確認して、next()
を呼びかえるだけでよい。ネームスペース毎に認証を分ける場合、io.of(“namespace”).use(callback)
とすれば良く、あとは同じである。
// global authentication
io.use(function(socket,next){
// 認証判定
var auth_error = ...
if( auth_error == true )
{
// authentication error
next( new Error("not authorized"));
return;
}
// ok
next();
});
問題なのは、どうやって認証を判断する材料を渡すかである。
Socket.IOにはデフォルトの認証方法などの実装がないので、各自で考える必要がある。
いろいろサイト回っていると、io.connect()の第2引数
にJson形式でqueryプロパティを指定すると、サーバー側に値が渡せる模様。ここでユーザIDとパスワード(暗号化したほうが良さそうだが)が渡せそうである。
// queryプロパティの値としてkey=value形式の文字列を指定すると、
//サーバー側に値を送信できる
var room1 = io.connect("http://localhost:3000/room1",
{query:{"user":"hoge","password":"hogehoge"}});
上記のようなJsonを指定した場合、サーバー側では以下のようにして取り出せる
// global authentication
io.use(function(socket,next){
// クライアントからの認証データを取り出す
var user = socket.handshake.query.user; // user = hoge
var password = socket.handshake.query.password; // password = hogehoge
// 認証ロジック
var auth_error = ...
if( auth_error == true )
{
// error
next( new Error("not authorized"));
return;
}
// ok
next();
});
パスワードなどは、クライアントで暗号化して、サーバーで復号化するしかなさそうである。
ちなみに第2引数で入力するパラメータは、GET送信のようにURLに展開される。上記の場合、下記でも同じ結果になる。
var room1 = io.connect("http://localhost:3000/room1?user=hoge&passowrd=hogehoge");
不具合なのか、仕様なのか良くわからないが、接続を2つ作成してそれぞれに違うパラメータを渡しても、先勝ち
で同じパラメータの内容になってしまう。
var room1 = io.connect("http://localhost:3000/room1?user=hoge&passowrd=hogehoge");
var room2 = io.connect("http://localhost:3000/room2?user=hoge2&passowrd=hogehoge2");
とすると、ちゃんとroom1とroom2のソケット通信はそれぞれ可能にはなる。が、room2の接続時のパラメータは、user=hoge,password=hogehoge
になってしまう。つまり上記の方法だと、room1とroom2で個別のユーザIDとパラメータを渡せないことになる。先勝ち
なので、
var room1 = io.connect("http://localhost:3000/room1");
var room2 = io.connect("http://localhost:3000/room2?user=hoge2&passowrd=hogehoge2");
とすると、room2にはパラメータが渡らないことになるので注意。これを回避する方法は今のところ不明である。
room1とroom2のセッションが一致するので、分解してパラメータ化するときに一緒になってしまうのかと。
データの送信方式
emit()
コール時にフラグを追加することによって、配信方法を指定することができる。
volatile
フラグを指定すると、送信失敗時にリトライしないので、失敗しても無視して大量データを配信する場合に適している。
フラグ | 配信方法 | 実装場所 |
---|---|---|
socket.emit() socket.broadcast.emit() | 文字列送信 | クライアント・サーバー |
socket.json.emit() socket.json.broadcast.emit() | Jsonオブジェクト送信 | クライアント・サーバー |
socket.volatile.emit() socket.volatile.broadcast.emit() | リトライ無し送信 | サーバー |