仕組み
Socket.IOサーバー (Node.js) と Socket.IOクライアント (ブラウザ、Node.js、または 別のプログラミング言語) 間の双方向チャネルは、可能な場合は常に WebSocket接続 で確立され、フォールバックとしてHTTPロングポーリングを使用します。
Socket.IOコードベースは、2つの異なるレイヤーに分割されています。
- 低レベルの配管:Socket.IO内部のエンジンであるEngine.IO
- 高レベルのAPI:Socket.IO自体
Engine.IO
Engine.IOは、サーバーとクライアント間の低レベル接続を確立する役割を担います。以下の処理を行います。
- さまざまなトランスポートとアップグレードメカニズム
- 切断検出
Engine.IOプロトコルの詳細バージョンはこちらにあります。
参照実装のソースコード(TypeScriptで記述)はこちらにあります。
- サーバー: https://github.com/socketio/engine.io
- クライアント: https://github.com/socketio/engine.io-client
- パーサー: https://github.com/socketio/engine.io-parser
トランスポート
現在、実装されているトランスポートは2つあります。
HTTPロングポーリング
HTTPロングポーリングトランスポート(単に「ポーリング」とも呼ばれます)は、連続したHTTPリクエストで構成されます。
- サーバーからデータを受信するための長時間実行される
GET
リクエスト - サーバーにデータを送信するための短時間実行される
POST
リクエスト
トランスポートの性質上、連続したエミットは連結され、同じHTTPリクエスト内で送信される場合があります。
WebSocket
WebSocketトランスポートは、サーバーとクライアント間の双方向で低レイテンシの通信チャネルを提供するWebSocket接続で構成されています。
トランスポートの性質上、各エミットは独自のWebSocketフレームで送信されます(一部のエミットは、2つの異なるWebSocketフレームになる場合もあります。詳細についてはこちらを参照してください)。
ハンドシェイク
Engine.IO接続の開始時に、サーバーはいくつかの情報を送信します。
{
"sid": "FSDjX-WRwSA4zTZMALqx",
"upgrades": ["websocket"],
"pingInterval": 25000,
"pingTimeout": 20000
}
sid
はセッションのIDであり、後続のすべてのHTTPリクエストでsid
クエリパラメーターに含める必要があります。upgrades
配列には、サーバーでサポートされているすべての「より良い」トランスポートのリストが含まれています。pingInterval
とpingTimeout
の値は、ハートビートメカニズムで使用されます。
アップグレードメカニズム
デフォルトでは、クライアントはHTTPロングポーリングトランスポートとの接続を確立します。
しかし、なぜでしょう?
WebSocketは双方向通信を確立する上で明らかに最適な方法ですが、企業のプロキシ、個人のファイアウォール、アンチウイルスソフトウェアなどが原因で、WebSocket接続を確立することが常に可能であるとは限りません。
ユーザーの観点からすると、WebSocket接続が失敗すると、リアルタイムアプリケーションがデータの交換を開始するまでに最大10秒の待機が発生する可能性があります。これは、ユーザーエクスペリエンスを感覚的に損ないます。
要約すると、Engine.IOは信頼性とユーザーエクスペリエンスを第一に考え、潜在的なUXの改善とサーバーパフォーマンスの向上を二の次にしています。
アップグレードするために、クライアントは次の操作を行います。
- 送信バッファーが空であることを確認します
- 現在のトランスポートを読み取り専用モードにします
- 他のトランスポートとの接続を確立しようとします
- 成功した場合は、最初のトランスポートを閉じます
ブラウザのネットワークモニターで確認できます。
- ハンドシェイク(セッションID — ここでは
zBjrh...AAAK
— が含まれており、後続のリクエストで使用されます) - データ送信(HTTPロングポーリング)
- データ受信(HTTPロングポーリング)
- アップグレード(WebSocket)
- データ受信(HTTPロングポーリング、4. のWebSocket接続が正常に確立されたら閉じられます)
切断検出
Engine.IO接続は、以下の場合に閉じられたと見なされます。
- HTTPリクエスト(GETまたはPOST)が失敗した場合(例:サーバーがシャットダウンした場合)
- WebSocket接続が閉じられた場合(例:ユーザーがブラウザでタブを閉じた場合)
- サーバー側またはクライアント側で
socket.disconnect()
が呼び出された場合
サーバーとクライアント間の接続がまだ稼働していることを確認するハートビートメカニズムもあります。
特定のインターバル(ハンドシェイクで送信されたpingInterval
値)で、サーバーはPINGパケットを送信し、クライアントは数秒(pingTimeout
値)以内にPONGパケットを返信する必要があります。サーバーがPONGパケットを受信しない場合、接続が閉じられたと見なします。逆に、クライアントがpingInterval + pingTimeout
以内にPINGパケットを受信しない場合、接続が閉じられたと見なします。
切断の理由は、こちら(サーバー側)およびこちら(クライアント側)に記載されています。
Socket.IO
Socket.IOは、Engine.IO接続にいくつかの追加機能を提供します。
- 自動再接続
- パケットバッファリング
- 確認応答
- すべてのクライアントまたはクライアントのサブセット(「ルーム」と呼びます)へのブロードキャスト
- 多重化(「名前空間」と呼びます)
Socket.IOプロトコルの詳細バージョンはこちらにあります。
参照実装のソースコード(TypeScriptで記述)はこちらにあります。