チャットを作ったり、複数人の画面を同時に更新したり、サーバー側で発生したイベントをブラウザにすぐ通知したり。
そんなときに出てくるのが、
WebSocket
と
Socket.IO
です。
何度か使ったことはあるのですが、しばらく触らないと、
「Socket.IOってWebSocketを簡単にしたものだっけ?」
「WebSocketとSocket.IOって同じもの?」
「Socket.IOってWebSocketのラッパー?」
と、毎回ちょっと分からなくなります。
ということで、未来の自分用に整理しておきます。
まずWebSocketとは?
WebSocketは、ブラウザとサーバーの間で双方向通信を行うための通信プロトコルです。
通常のHTTP通信は、大まかには、
ブラウザ
│
│ リクエスト
↓
サーバー
│
│ レスポンス
↓
ブラウザ
という流れです。
基本的にはブラウザ側から「データください」と要求して、それにサーバーが答えます。
一方WebSocketでは、一度接続を確立すると、
ブラウザ
⇅
⇅
サーバー
という状態を維持できます。
そのため、ブラウザからサーバーだけではなく、
サーバーからブラウザへ好きなタイミングでデータを送る
ことができます。
例えば、
チャット
オンラインゲーム
リアルタイム通知
株価やセンサー値のリアルタイム表示
オンラインクイズ
複数人で共有するWebアプリ
などに向いています。
ブラウザにはWebSocketが最初からある
WebSocketはブラウザの標準APIとして利用できます。
例えばJavaScriptなら、
// WebSocketサーバーへ接続
const socket = new WebSocket("wss://example.com/ws");
// 接続完了
socket.addEventListener("open", () => {
console.log("接続しました");
// サーバーへメッセージを送信
socket.send("こんにちは");
});
// サーバーからメッセージを受信
socket.addEventListener("message", (event) => {
console.log("受信:", event.data);
});
// 接続終了
socket.addEventListener("close", () => {
console.log("切断しました");
});
これだけでWebSocket通信ができます。
クライアント側でSocket.IOのような追加ライブラリを必ず入れなければならないわけではありません。
ではSocket.IOとは?
Socket.IOは、クライアントとサーバー間で、
低遅延・双方向・イベントベースの通信を実現するためのライブラリ
です。
例えばSocket.IOでは、
// チャットメッセージを送信
socket.emit("message", "こんにちは");
とか、
// 抽選開始を通知
socket.emit("drawStart");
とか、
// 誰かが参加したことを通知
socket.emit("participantJoined", user);
のように、
「イベント名」を付けて通信できます。
受信側も、
// drawStartイベントを受信
socket.on("drawStart", () => {
console.log("抽選開始!");
});
と書けます。
これが非常に分かりやすい。
Socket.IOはWebSocketのラッパーなのか?
ここが一番分かりにくかったところ。
ざっくり説明すると、
「WebSocketを便利に扱えるラッパー的なもの」
と考えても大きくは外れていません。
ただし、厳密には、
単純にWebSocket APIを包んだだけのラッパーではありません。
Socket.IOの内部は、大まかに次のような構造になっています。
アプリケーション
↓
Socket.IO
↓
Engine.IO
↓
┌─────┼───────────┐
↓ ↓ ↓
HTTP WebSocket WebTransport
long-
polling
Socket.IOには、低レベルの通信部分を担当する Engine.IO という仕組みがあります。
Engine.IOが実際の通信手段を管理し、その上にSocket.IOの便利な機能が載っています。
つまり、
WebSocket
↑
通信手段の一つとして利用
Socket.IO
↑
その上でイベントなどの機能を提供
という関係です。
Socket.IOはWebSocketそのものではない
ここは重要。
Socket.IO公式にも、
Socket.IOはWebSocketの実装そのものではない
とはっきり書かれています。
Socket.IOはWebSocketを通信手段として使用できますが、その通信データにSocket.IO独自の情報を追加します。
例えば、
socket.emit("hello", "world");
というSocket.IOの通信は、内部ではSocket.IOやEngine.IO用の情報が追加されたデータとして送信されます。
つまり、
普通のWebSocket
と、
Socket.IO over WebSocket
は完全に同じ通信ではありません。
そのため、
普通のWebSocketクライアント
↓
Socket.IOサーバー
にそのまま接続することはできません。
反対に、
Socket.IOクライアント
↓
普通のWebSocketサーバー
もそのままでは通信できません。
じゃあ「別のプロトコル」なの?
これも言い方に注意が必要。
Socket.IOにはSocket.IO独自のプロトコルがあります。
ただし、WebSocketとは全く無関係な通信方式という意味ではありません。
例えばWebSocketを利用している場合は、
Socket.IOプロトコル
↓
Engine.IOプロトコル
↓
WebSocket
↓
TCP
というように層になっています。
だから、
「WebSocketとは別物」
だけで説明すると少し雑。
逆に、
「Socket.IOはWebSocketのラッパー」
だけでも少し足りません。
個人的には、
Socket.IOはWebSocketなどの通信手段を利用し、その上にイベント通信や再接続などの便利な機能を追加するライブラリ
と覚えるのが一番分かりやすいと思います。
Socket.IOは何を便利にしてくれる?
では、普通のWebSocketでも通信できるのに、なぜSocket.IOを使うのでしょうか。
Socket.IOにはリアルタイムアプリで欲しくなる機能が最初からかなり用意されています。
1. イベント通信
先ほどの、
// ユーザー参加
socket.emit("join", user);
// 抽選開始
socket.emit("drawStart");
// 抽選結果
socket.emit("drawResult", result);
のような書き方です。
WebSocketの場合でも同じ仕組みは作れます。
例えば、
// メッセージ形式を自分で決める
socket.send(JSON.stringify({
type: "drawResult",
data: result
}));
として、
受信側で、
// JSONへ変換
const message = JSON.parse(event.data);
// メッセージの種類によって処理
if (message.type === "drawResult") {
console.log(message.data);
}
とすれば実現できます。
ただし、その仕組みを自分で設計する必要があります。
Socket.IOでは、最初からイベントとして扱えるようになっています。
2. 自動再接続
リアルタイム通信では、接続が切れることがあります。
例えば、
Wi-Fiが一瞬切れた
スマートフォンの通信環境が変わった
ネットワークが不安定だった
など。
普通のWebSocketを使う場合、必要なら再接続処理を自分で設計します。
Socket.IOには自動再接続の仕組みがあります。
接続が切れた場合、一定間隔で何度も猛烈に接続するのではなく、待ち時間を増やしながら再接続を試みます。
3. HTTP long-pollingへのフォールバック
Socket.IOではWebSocketだけを使うとは限りません。
現在は主に、
HTTP long-polling
WebSocket
WebTransport
というTransportを扱います。
例えばネットワークやプロキシの都合でWebSocketを利用できない場合でも、HTTP long-pollingを利用できます。
つまり、
Socket.IO
│
├─ WebSocket
│
├─ WebTransport
│
└─ HTTP long-polling
のように、実際にデータを運ぶ部分をSocket.IO側が扱ってくれます。
4. Room
Socket.IOで個人的に分かりやすい機能がRoomです。
例えば、
room-A
├─ ユーザー1
├─ ユーザー2
└─ ユーザー3
room-B
├─ ユーザー4
└─ ユーザー5
というグループを作れます。
Node.jsのSocket.IOサーバーなら、
// room-Aに参加
socket.join("room-A");
そして、
// room-Aだけにイベント送信
io.to("room-A").emit("message", "こんにちは");
とできます。
チャットルームだけでなく、
オンラインゲームの部屋
オンラインクイズ
投票
抽選
授業ごとのリアルタイム画面
などにも使えます。
5. ブロードキャスト
接続している複数のクライアントへ一斉に送ることも簡単です。
// 全クライアントへ送信
io.emit("message", "全員に送信");
Roomを使えば、
// 特定Roomだけに送信
io.to("room-A").emit("message", "Aだけ");
とできます。
6. ACK
Socket.IOにはAcknowledgement、略してACKという仕組みもあります。
送信するだけではなく、
「相手側で処理された結果を返してもらう」
ことができます。
例えば、
// サーバーへ参加要求を送信
socket.emit("joinRoom", "room-A", (response) => {
console.log(response);
});
サーバーでは、
socket.on("joinRoom", (roomId, callback) => {
// Roomへ参加
socket.join(roomId);
// クライアントへ結果を返す
callback({
success: true
});
});
といった書き方ができます。
リアルタイム通信ですが、APIのレスポンスのような感覚でも扱えます。
WebSocketとSocket.IOを比較
ざっくり整理するとこう。
| 項目 | WebSocket | Socket.IO |
|---|---|---|
| 主な位置付け | 通信プロトコル / Web API | リアルタイム通信ライブラリ |
| ブラウザ標準 | ○ | × |
| 双方向通信 | ○ | ○ |
| イベント名による通信 | 自分で設計 | ○ |
| 自動再接続 | 自分で実装 | ○ |
| Room | 自分で実装 | ○ |
| ブロードキャスト | 自分で実装 | ○ |
| ACK | 自分で実装 | ○ |
| HTTP long-polling | 別途実装 | ○ |
| WebTransport | 別途実装 | ○ |
| 独自プロトコル | ― | ○ |
| 実装の自由度 | 高い | 高機能 |
| 手軽さ | シンプル | アプリ向け機能が豊富 |
じゃあどっちを使う?
単純に、
ブラウザ ⇔ サーバー
でデータをリアルタイムに送りたいだけなら、WebSocketで十分な場合があります。
余計な仕組みも少なく、WebSocketという標準的な通信方式をそのまま利用できます。
一方、
複数ユーザー
Room
イベント
再接続
ACK
ブロードキャスト
などが必要になるアプリなら、Socket.IOを使うと実装がかなり楽になります。
例えば、
チャット
オンラインクイズ
リアルタイム抽選
マルチプレイヤーゲーム
リアルタイムダッシュボード
複数人で同じ状態を共有するWebアプリ
などです。
最後に
今回、自分が忘れないために一番覚えておきたいのはこれ。
WebSocket
=双方向通信を行うための標準的な仕組み
Socket.IO
=WebSocketなどを利用して
リアルタイムアプリを作りやすくするライブラリ
そしてもう一つ。
Socket.IO ≠ WebSocket
Socket.IOはWebSocketを使えます。
でも、単純なWebSocket APIのラッパーではなく、Engine.IOやSocket.IO独自のプロトコルを持っています。
たぶん数か月後にはまた、
「Socket.IOってWebSocketそのものだっけ?」
となる気がするので、そのときはこの記事を見返そうと思います。

0 件のコメント:
コメントを投稿