socket.ioのパケットを(触りだけ)キャプチャ

  • 44
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

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時点では

  1. polling(jsonp/xhr)
  2. 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(接続確認)

基本的な流れ.png

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)"として終えます。
  • pollingwebsocketupgrade が完了します。
  • 後述しますが実際は、"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
  • client側は、ページ情報を取得した「主接続用ポート」で POST メソッドを通じでデータを送信します。
    server側は、 GET メソッドを用いたpollingに対する応答として、データを送信します。

  • pollingで用いるポートは、出足は「主接続ポート」を用い、別途並列で確保を試みる「polling用ポート」が開き次第、新しく確保した後者のポートを用います。

polling中.png

pollingとwebsocketの確立のタイミング

  • pollingとwebsocketの確立タイミングは、下図のように並列で動く様です。 最終的に、websocketのupgradeパケットまでにpolling経路が終端され、websoketに upgrade されます。

pollingとwebsocketの確立タイミング.png

経路断時の振る舞い

  • 前述 Step6 で記した様にwebsocket層で、keep-alive的な通信確認を実施しています。
  • 経路が途絶える(LANケーブルを抜く)と、指定timeout時間(Step2のserverからの指定間隔)を超えなければ、接続は保たれます。
    その間、送信されたデータは、経路復旧時に再送されます。下図 CaseA参照。

  • 指定timeout時間を超えた後に経路復旧すると、 Step 2 より再開します。

  • いずれしても、TCPレベルで接続が切れていなければの話となります。

経路断時の振る舞い.png

server側からのclose

  • websocket経路で、server → client へcloseが送信され、 その後逆に、client→serverに同様のcloseが送信されます。

その後 FIN がserver側から送信されます。

  • この時、「主接続用」と「polling用」はcloseされずに、保持されます。
  • また、server側に、再接続コードを埋め込むと、「主接続用」と「polling用」は再利用されます。
    • 完全にcloseする方法、要確認。。 再接続コードを記述しないと、pollingが勝手に始まるっぽい。。。(正しくpollingされているかまでは、未調査)

server_close.png

  • 利用コード。(サーバ側)
    :
  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_close.png

  • 利用コード。(client側)
    :
   socket.on('timer', function(msg){
       console.log('timer rise!');
       socket.close();
       socket = io();
      });
    :

利用環境

調査不足のところ

  • transports の指定方法
  • ping-pong間隔指定方法
  • close方法(接続の後始末) 及び 再接続方法。
    • sessino管理
  • バイナリ送受
  • etc..