Socket.ioのRoomsを学習する

以前の検証で「リアルタイム双方向通信」が可能になったので、定番のチャットアプリでも作ってみようと思う。

いずれまとめ記事をエントリーするつもりだが、しばらくは1機能ずつしっかり学習して理解を深めて行きたい。

そしてどうせチャットを作るなら、ロビーと部屋の機能がある物が良い。

WEB アプリで使う時も、特定のユーザーのみでデータをやり取りしたいと思っているので、この機能の習得は必須になってきそうだ。

 

色々試すにあたって、本エントリーでは PHPで作ったサイトでSocket.ioを使いたい 内に掲載した「server.js のソース」のソースをいじくり回しているので、同じことをしてみたい場合は先にそちらを理解してくることをおススメする。

 

取り合えず一覧を取得してみる

部屋の一覧情報は adapter.rooms で簡単に取得する事が可能らしいので、まずは以下のシンプルなコードを実行してみた。

io.of("/").on("connection", socket => {
	console.log(socket.id);
	console.log(io.of("/").adapter.rooms);
});
$ node server.js
<socket.id-A>
Map(1) { '<socket.id-A>' => Set(1) { '<socket.id-A>' } }

ソケット ID は重複しない仕様だから Set Object として一覧を持っているのは理解できるが、Map のキー部分がソケット ID なのは何故?と言う疑問が。

と言うか、何故部屋の一覧でこれが出て来るのか。

少し調べると adapter.sids と言う、その名の通りソケット ID の一覧を返すと言う機能もあったので見比べてみる事にした。

変化が分かりやすいように別タブで開いて、接続数を2にした状態で実行。

io.of("/").on("connection", socket => {
	console.log(socket.id);
	console.log(io.of("/").adapter.rooms);
	console.log(io.of("/").adapter.sids);
});
$ node server.js
<socket.id-A>
Map(1) { '<socket.id-A>' => Set(1) { '<socket.id-A>' } }

<socket.id-B>
Map(2) {
	'<socket.id-A>' => Set(1) { '<socket.id-A>' },
	'<socket.id-B>' => Set(1) { '<socket.id-B>' }
}
Map(2) {
	'<socket.id-A>' => Set(1) { '<socket.id-A>' },
	'<socket.id-B>' => Set(1) { '<socket.id-B>' }
}

うん、やはりこれはユーザーの一覧では?w

どちらも全く同じ結果が帰ってきたので疑問は深まるばかり、答えを求めてひたすら検索を繰り返す。

 

ソケット生成時の仕様

そのものズバリの答えは発見できなかったが、公式ドキュメントの Socket.io Version: 4.x Rooms に書かれていた Room events を試した事で、なぜこうなるのかが理解出来た。

結論から書くと、どうやらソケットが生成される際に「自動的に自分一人の部屋を作ってそこに参加する」と言う仕様らしい。

これは実際の挙動を見ると分かりやすい。(接続数1)

// 誰かが接続したら実行される
io.of("/").on("connection", socket => {
	console.log(socket.id);
	console.log(io.of("/").adapter.rooms);
	console.log(io.of("/").adapter.sids);
});

// 何らかの部屋が作られたら実行される
io.of("/").adapter.on("create-room", room => {
	console.log("create room: " + room);
});

// 誰かがどこかの部屋に入ると実行される
io.of("/").adapter.on("join-room", (room, id) => {
	console.log("join room: " + room + " => " + id);
});
$ node server.js
create room: <socket.id-A>
join room: <socket.id-A> => <socket.id-A>
<socket.id-A>
Map(1) { '<socket.id-A>' => Set(1) { '<socket.id-A>' } }
Map(1) { '<socket.id-A>' => Set(1) { '<socket.id-A>' } }

ユーザーは単純にページを表示(ソケットを生成)しただけで、生成されたソケット ID の名前の部屋を作り、そこに参加する所までは自動で行われている事が分かる。

ちょっと意外だったのは io.of(“/").on より io.of(“/").adapter.on の方が先に実行された所、これはイベント発生の仕様を深堀すれば答えは見つかるが、現時点では特に疑問に思わないので「そう言うモノ」で良いかな。

 

Room に join する

プログラム系の記事を書いていると、ルー語みたいになってくる。

上記仕様を踏まえて次の検証、Socket.io がデフォルトで内蔵している Rooms には join と言う「部屋に入るための機能」が用意されている。

これは任意の部屋名に入室出来るので、きっと adapter.rooms と adapter.sids に変化が出るハズだ。

早速試してみよう(接続数2)

io.of("/").on("connection", socket => {
	console.log(socket.id);
	socket.join("room1");
	console.log(io.of("/").adapter.rooms);
	console.log(io.of("/").adapter.sids);
});

Line: 3 で自分のソケットを「room1」と言う部屋に参加させる。

$ node server.js
create room: <socket.id-A>
join room: <socket.id-A> => <socket.id-A>
<socket.id-A>
create room: room1
join room: room1 => <socket.id-A>
Map(2) {
	'<socket.id-A>' => Set(1) { '<socket.id-A>' },
	'room1' => Set(1) { '<socket.id-A>' }
}
Map(1) {
	'<socket.id-A>' => Set(2) { '<socket.id-A>', 'room1' }
}

create room: <socket.id-B>
join room: <socket.id-B> => <socket.id-B>
<socket.id-B>
join room: room1 => <socket.id-B>
Map(3) {
	'<socket.id-A>' => Set(1) { '<socket.id-A>' },
	'room1' => Set(2) { '<socket.id-A>', '<socket.id-B>' },
	'<socket.id-B>' => Set(1) { '<socket.id-B>' }
}
Map(2) {
	'<socket.id-A>' => Set(2) { '<socket.id-A>', 'room1' },
	'<socket.id-B>' => Set(2) { '<socket.id-B>', 'room1' }
}

ちょっと見づらくて申し訳ないが、これなら意味が分かりますね。

注目すべき点は Line: 5 だろうか、公式ドキュメントを読むに Room events としては

  • create-room
  • delete-room
  • join-room
  • leave-room

この4つが用意されているが、部屋の入退室機能としては

  • join
  • leave

の2しかないようなのだ。(少なくとも Rooms のドキュメント内には存在しない。)

色々と検証している感じでは「名前だけ付けた無人の部屋」は作れないようになっているようで、確かにそんなものが作れてしまうと消し忘れなどで、何もしない無駄なスペースを確保し続けると言う事が起こるので納得の仕様である。

つまり join した時に指定の名前の部屋が無ければ create され、leave した時に部屋の人数が0人になれば delete される、この理解で多分大丈夫。

 

まとめ

  • ソケットが生成されたら、自動的に自らのソケット ID の部屋を作って参加する。
  • 任意の名前の部屋を作る時は、いきなり join すれば自動的に作られる。
  • 部屋を削除したい時は、参加している全てのユーザーを leave する必要がある。
  • adapter.rooms はソケット生成時に自動で作られるものも含め、全ての部屋名をキーとした Map Object に、それに参加している全てのソケット ID のリストを Set Object で持ったものを返す。
  • adapter.sids は全てのソケット ID をキーとした Map Object に、そのソケット ID が参加している全ての部屋名のリストを Set Object で持ったものを返す。

長くなってしまったが、この5つは必ず理解しておきたい所だ。

 

関連記事

PHPで作ったサイトでSocket.ioを使いたい

 

JavaScriptNode.js,Socket.io

Posted by theuri