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

[socket.io](http://socket.io/)(利用バージョンは[@1.35](https://github.com/Automattic/socket.io/tree/1.3.5))がどんな通信をしているのかな？と思い、Web知識の浅い、Node.jsの扉を叩いている程度のレベル(websocketってなに？のレベル)の私が、[socket.io](http://socket.io/)の理解を目的として、googleでの情報収集＋実際にキャプチャ(触り程度です。パターンも少ないです。。)しながら、備忘の為にまとめた内容です。
従って、誤りや、ずれてるところも多々あると思います。

# socket.io概要

[socket.io](http://socket.io/) は シンプルなAPIでリアルタイムWebを実現する為のモジュール。

ググってみると、同モジュールは [websocket](https://tools.ietf.org/html/rfc6455) と ロングポーリング( __polling__ ) をサポートしており、 モジュール内部で自動に最適な接続方法( __transports__ )を選択し、通信経路を提供します。[こちら](http://qiita.com/hnakamur/items/1f230c441ce38532be26)を参照すると、 __polling__ はファイアウォール対策のだそうです。
> ポーリングについては、[こちら](http://itpro.nikkeibp.co.jp/article/COLUMN/20080220/294242/) などが勉強になるかと。

古いバージョンでは以下の __transports__ 群をサポートしていたそうです。

* websocket
* flashsocket
* htmlfile
* xhr-polling
* jsonp-polling

が、[socket.io@1.35](https://github.com/Automattic/socket.io/tree/1.3.5)時点では

1. polling(jsonp/xhr)
2. websocket

の二つのみとなっています。([こちら](https://github.com/Automattic/engine.io#transports)より)
該当バージョンでは、最初に __polling__ で試して、その後可能だったら、 __websocket__ に自動で更新( __upgrade__ )されるそうです。

このあたりのプロトコル周りは、"@1.00"付近から分離したらしい [engine.io](https://github.com/Automattic/engine.io) ( [engine.io-protocol](https://github.com/Automattic/engine.io-protocol) など) が担とのことで、 同モジュール次第ですね( 現時点サポートのサポートは、https://github.com/Automattic/engine.io#transports を参照)。

初めから __transports__ に限定する(例えば __websocket__ のみなど)ことも可能らしいのですが、 下名の理解が、浅い為、[socket.io@1.35](https://github.com/Automattic/socket.io/tree/1.3.5) で設定する方法をみつけることはできませんでした。
> __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](https://qiita-image-store.s3.amazonaws.com/0/91771/d39bc29f-ae0a-2fa2-f88b-629df09002b4.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](https://tools.ietf.org/html/rfc6455#section-4) で規定される __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(暗号化)されます。 ( [こちら](https://tools.ietf.org/html/rfc6455#section-10.3) より )


### 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](https://qiita-image-store.s3.amazonaws.com/0/91771/70820fdc-baa2-7979-1905-b72c55d0f2dc.png)


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

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

![pollingとwebsocketの確立タイミング.png](https://qiita-image-store.s3.amazonaws.com/0/91771/526babee-f9e9-48d7-367e-e5007d2908db.png)

## 経路断時の振る舞い

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

* 指定timeout時間を超えた後に経路復旧すると、 __Step 2__ より再開します。
* いずれしても、TCPレベルで接続が切れていなければの話となります。

![経路断時の振る舞い.png](https://qiita-image-store.s3.amazonaws.com/0/91771/f61e29a2-245e-913f-f5e3-f5de1661ad1c.png)

## server側からのclose

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

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

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

![server_close.png](https://qiita-image-store.s3.amazonaws.com/0/91771/5468425c-f6a1-2212-a8f5-5c18a44ead6a.png)

* 利用コード。(サーバ側)

~~~javascript
    :
  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](https://qiita-image-store.s3.amazonaws.com/0/91771/e1dd4a5f-9d16-1fe4-7522-1a2416fe6dd2.png)

* 利用コード。(client側)

~~~javascript
    :
   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..