socket.io(利用バージョンは@1.35)がどんな通信をしているのかな?と思い、Web知識の浅い、Node.jsの扉を叩いている程度のレベル(websocketってなに?のレベル)の私が、socket.ioの理解を目的として、googleでの情報収集+実際にキャプチャ(触り程度です。パターンも少ないです。。)しながら、備忘の為にまとめた内容です。
従って、誤りや、ずれてるところも多々あると思います。
socket.io概要
socket.io は シンプルなAPIでリアルタイムWebを実現する為のモジュール。
ググってみると、同モジュールは websocket と ロングポーリング( polling ) をサポートしており、 モジュール内部で自動に最適な接続方法( transports )を選択し、通信経路を提供します。こちらを参照すると、 polling はファイアウォール対策のだそうです。
ポーリングについては、こちら などが勉強になるかと。
古いバージョンでは以下の transports 群をサポートしていたそうです。
- websocket
- flashsocket
- htmlfile
- xhr-polling
- jsonp-polling
が、socket.io@1.35時点では
- polling(jsonp/xhr)
- websocket
の二つのみとなっています。(こちらより)
該当バージョンでは、最初に polling で試して、その後可能だったら、 websocket に自動で更新( upgrade )されるそうです。
このあたりのプロトコル周りは、"@1.00"付近から分離したらしい engine.io ( engine.io-protocol など) が担とのことで、 同モジュール次第ですね( 現時点サポートのサポートは、https://github.com/Automattic/engine.io#transports を参照)。
初めから transports に限定する(例えば websocket のみなど)ことも可能らしいのですが、 下名の理解が、浅い為、socket.io@1.35 で設定する方法をみつけることはできませんでした。
transports に限定は、io.set()ではうまくいかなかった。。
ちなみに、 polling なら古いブラウザでもいけるかなと思い
- Mozilla/5.0 (Solaris10_9-19 i86pc) Gecko/20090623
で試したところ、 polling 接続が行えませんでした。
パケットの流れ
ざっくり以下のパターンをキャプチャしてみました。
- 基本的な接続~送受
- polling中(websocket確立前)のデータ送受
- pollingとwebsocketの確立のタイミング
- 経路断時の振る舞い
- server側からのclose
- client側からのclose
基本的な接続~送受
大きな流れは、以下の通りです。
- Step 1 HTML及びsocket.io.js他の取得
- Step 2 socket.io セッション開始
- Step 3 polling開始
- Step 4 websocketへのUpgrade
- Step 5 websocket経路のデータ送受
- Step 6 Ping-Pong(接続確認)
Step1 HTML及びsocket.io.js他の取得
- 通常のHTTPコネクションです。ページ情報や、スクリプト情報を取得します。
Step2 socket.io セッション開始
- clientのスクリプト内の接続指示(
var socket = io();
)により、GETメソッドで接続要求がなされます。serverは応答で以下を返します。- session id(sid) ← websocket内で用いるKEY情報とは異なります。
- websocketへの upgrade 可能か。
- websocket Ping 間隔
- websocket Ping Timeout間隔
- Ping周りの設定方法は現時点、未調査。
- HTTPメッセージを用います。
Step3 polling開始
- clientよりポーリングを開始します。
この例では、ポーリング中にメッセージの送受が無かったものとしています。
この時点で、メッセージの送受が発生した場合については、後述します。 - HTTPメッセージを用います。
Step4 websocketへのUpgrade
- clientよりwebsocket用の新しいポートで接続を確立し、 websocket で規定される opening handshake を行います。
- HTTPメッセージを用います。
- websocketパケットを用いて、送受を確認します。(ping-pong)。
- この時、既に発行ずみのpolling要求は"noop(no operation)"として終えます。
- polling → websocket の upgrade が完了します。
- 後述しますが実際は、"polling開始"と並列で動作するようです。
websocketのデータフレーム
#
# "RFC6455":https://tools.ietf.org/html/rfc6455 より抜粋
#
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
- 上記の様に、通常のHTTP通信の様にHTTPヘッダが付きません。
- clientからのパケットは、セキュリティ上Mask(暗号化)されます。 ( こちら より )
Step5 websocket経路のデータ送受
- 任意のタイミングで送受します。
Step6 Ping-Pong(接続確認)
- 接続確認の為、定期的(Step2のserverからの指定間隔)にclientからPingを送信し、serverはPingでそれにこたえます。
- websocketパケットを用います。
- Pongが途絶えても、指定timeout時間(Step2のserverからの指定間隔)を超えなければ、接続は保たれます。(詳細後述します。)
polling中(websocket確立前)のデータ送受
-
ここでは、polling中(すなわちwebsocket確立前)に、データの送受が発生した場合についてまとめます。大きな流れは以下の通りです。
- 「Step 3 polling開始」内
- Step 3-1 主接続ポートでのpolling
- Step 3-2 polling用ポートでのpolling
- 「Step 3 polling開始」内
-
client側は、ページ情報を取得した「主接続用ポート」で POST メソッドを通じでデータを送信します。
server側は、 GET メソッドを用いたpollingに対する応答として、データを送信します。 -
pollingで用いるポートは、出足は「主接続ポート」を用い、別途並列で確保を試みる「polling用ポート」が開き次第、新しく確保した後者のポートを用います。
pollingとwebsocketの確立のタイミング
- pollingとwebsocketの確立タイミングは、下図のように並列で動く様です。
最終的に、websocketのupgradeパケットまでにpolling経路が終端され、websoketに upgrade されます。
経路断時の振る舞い
-
前述 Step6 で記した様にwebsocket層で、keep-alive的な通信確認を実施しています。
-
経路が途絶える(LANケーブルを抜く)と、指定timeout時間(Step2のserverからの指定間隔)を超えなければ、接続は保たれます。
その間、送信されたデータは、経路復旧時に再送されます。下図 CaseA参照。 -
指定timeout時間を超えた後に経路復旧すると、 Step 2 より再開します。
-
いずれしても、TCPレベルで接続が切れていなければの話となります。
server側からのclose
- websocket経路で、server → client へcloseが送信され、
その後逆に、client→serverに同様のcloseが送信されます。
その後 FIN がserver側から送信されます。
- この時、「主接続用」と「polling用」はcloseされずに、保持されます。
- また、server側に、再接続コードを埋め込むと、「主接続用」と「polling用」は再利用されます。
- 完全にcloseする方法、要確認。。
再接続コードを記述しないと、pollingが勝手に始まるっぽい。。。(正しくpollingされているかまでは、未調査)
- 完全にcloseする方法、要確認。。
- 利用コード。(サーバ側)
:
setTimeout(function() {
io.close();
http.listen(3000, function(){ // 再度listenしないと、再接続しない。
console.log('listening on *:3000 --');
});
}, 10000);
:
client側からのclose
-
websocket経路で、client→server へcloseが送信され、
その後 FIN がserver側から送信されます。 -
この時、「主接続用」と「polling用」はcloseされずに、保持されます。
-
再接続コード、要調査。
- 利用コード。(client側)
:
socket.on('timer', function(msg){
console.log('timer rise!');
socket.close();
socket = io();
});
:
利用環境
-
server
- node.js v0.12.2 (windows)
socket.io@1.35
-
client a
- Mozilla Firefox 38.0.1 / CentOS6.5-64
-
client b
- Google Chrome 43.0.2357.81 / Windwos7-32
-
利用コードは、socket.ioでサンプルとして記載されていた http://socket.io/get-started/chat/ を適宜いじって確認しました。編集したコードは "こちら":https://github.com/gitseitanaka/study-socket.io-basic 。
調査不足のところ
- transports の指定方法
- ping-pong間隔指定方法
- close方法(接続の後始末) 及び 再接続方法。
- sessino管理
- バイナリ送受
- etc..