Socket.IO 1.0の変更点・内部的な話

Socket.IO 1.0の変更点・内部的な話

socket.io 1.0と0.9の比較や内部実装について。 node学園#13

6aea7f3a669da60c1a5744d8b22bd6e6?s=128

Naoyuki Kanezawa

June 23, 2014
Tweet

Transcript

  1. Socket.IO 1.0の変更点、内 部的な話 Node学園#13

  2. 自己紹介 github: nkzawa twitter: nkzawa socket.io, engine.ioなどにコントリビュートしたり、 socket.io関連モジュールをつくったり。

  3. アジェンダ 1. Engine.IOとUpgrade 2. Middleware 3. バイナリサポート 4. Adapter 5.

    プロトコル
  4. Engine.IO と Upgrade

  5. engine.io • トランスポートの違いを吸収して、websocketと同等の機能 を提供するライブラリ。 • socket.io 1.0には名前空間やルーム、自動再接続などの 高レベルな機能のみ実装され、メッセージの送受信は engine.ioを通して行われる。 •

    socket.io 0.9で採用されていたfallback方式ではなく upgrade方式でトランスポートを切り替える。
  6. fallback 1. 最初にwebsocketで接続を試みる。 2. 接続できたら終了。 3. 接続できなかった場合、初期設定で最低10秒間タイムアウ トを待つ。 4. タイムアウトしたら、改めてpollingで接続を行う。

    => websocketが使えない場合の初回接続速度に問題があっ た。
  7. upgrade 1. httpのgetリクエストを行い、pollingの接続を確立させる。 2. upgradeできるか判定。設定次第でupgradeは行われず終 了。 3. pollingを維持したまま、並列にwebsocketで通信しパケット の交換ができるかどうかまで試す(probe)。 4.

    websocketがつながったらpollingが止まるのを待ち(メッ セージのロスを防ぐため)、メインのトランスポートを切り替 える。
  8. サポートされるトランスポート • websocket • polling ◦ xhr ◦ jsonp ※polling時はxhrが優先的に使用される。

  9. flashsocket ? 公式にはサポートされなくなった。flashsocketに限らず、別のト ランスポートが必要な場合は自作して追加する。 “removed flashsocket, moving to userland” https://github.com/Automattic/engine.

    io/commit/10261c52119f2912b72b55bec4df87d91b180525
  10. upgrade関連オプション(client側) transports: [‘polling’, ‘websocket’] 使用するトランスポートを順番に設定する。[‘websocket’] と すると最初からwebsocketで接続することもできる。 rememberUpgrade: false 一度upgradeに成功した後に再接続すると、upgradeプロセ

    スを飛ばしてwebsocketで接続する。SSLの時などに有効 にするとよいらしい。
  11. Middleware

  12. middleware ハンドシェイクから接続までの間に、処理を挿入する仕組み。 expressのmiddlewareと似たようなもの。 io.use(function(socket, next) {});

  13. 0.9では 0.9ではauthorizationで同様のことができたが、あくまで認証用 の機能で拡張性は低かった。 io.set(‘authorization’, function(handshakeData, fn) { // 認証失敗 fn(null,

    false); });
  14. 1.0の認証 setメソッドとauthorizationは廃止(後方互換のため残されては いる)。代わりにmiddlewareを使って認証する。 io.use(function(socket, next) { // 接続を閉じる next(new Error(‘not

    authorized’);); });
  15. ハンドシェイクデータ ハンドシェイク時のrequestオブジェクトにアクセスでき、そこか らcookieのパースやセッションの復元などが可能。 io.use(function(socket, next) { console.log(socket.request); next(); });

  16. // expressのmiddlewareを使ってcookieをパースする var cookieParser = require(‘cookie-parser’)(‘my secret’); io.use(function(socket, next) {

    var req = socket.request; var res = {}; // ダミーのレスポンス cookieParser(req, res, next); }); io.on(‘connection’, function(socket) { console.log(socket.request.cookies); });
  17. ネームスペース middlewareはネームスペース単位。 // デフォルトネームスペース。次の二つは同じ意味。 io.use(function(socket, next) {}); io.of(‘/’).use(function(socket, next) {});

    // foo ネームスペース io.of(‘/foo’).use(function(socket, next) {});
  18. 実行順序 全てのクライアントは、デフォルトネームスペース(’/’)へ必 ず最初に接続され、’/’ のmiddlewareが実行される。 cookieパーサのように、常に先に実行したいmiddlewareは ‘/’ へ登録する。

  19. // クライアント var socket = io(‘http://localhost:3000/foo’); // サーバ io.use(function(socket, next)

    { console.log(‘first’); // 先に実行される next(); }); io.of(‘/foo’).use(function(socket, next) { console.log(‘second’); // 後で実行される next(); });
  20. バイナリサポート

  21. バイナリサポート 1.0からバイナリデータを送受信できるようになった。オブジェク トや配列に埋め込むんだり、複数バイナリを同時送受信も可 能。 socket.emit(‘event’, new Buffer([0, 1, 2]));

  22. examples // オブジェクトに埋め込み socket.emit(‘event’, {data: new ArrayBuffer(10)}); // 複数バイナリ socket.emit(‘event’,

    [binary1, binary2]); // acknowledge socket.on(‘event’, function(callback) { callback(new Buffer([1, 2, 3])); });
  23. 0.9でバイナリを扱う 0.9でもバイナリをbase64エンコードした文字列として送受信す ることはできた。ただし、エンコード/デコード処理が入り、データ サイズも増加するため効率はよくない。 socket.emit(‘event’, buffer.toString(‘base64’));

  24. サポートされるデータ形式 • Buffer (node) • ArrayBuffer (node, browser) • Blob,

    File (browser)
  25. バイナリ送信できるトランスポート • ◯ websocket • ◯ XMLHttpRequest Level 2 •

    ☓ 古い仕様のwebsocket • ☓ XMLHttpRequest • ☓ JSONP ※バイナリ送信をサポートしないトランスポートでも、base64エンコードされた文字列 として透過的にデータを送受信できる。
  26. バイナリの扱いに注意 データサイズに制限はないが、メモリへ読み込むので、大きな ファイルの配信などには向かない。 この問題を解決するために、別モジュールによるstream送信の サポートが計画されている。

  27. Adapter

  28. Adapter 複数のサーバ間でメッセージをbroadcastする時に 使用される。0.9にあったStoreの代替。 io.adapter(Adapter);

  29. socket.io-redis 0.9にあったRedisStore相当のadapter。socket.io本体には同 梱されていないので、別途インストールが必要。 var redis = require(‘socket.io-redis’); io.adapter(redis({host: ’localhost’, port:

    6379}));
  30. Storeとの違い • Storeのget/set 相当のデータ共有機能がなくな りシンプルに。Adapterが扱うのはbroadcastの み。 • 内部的に使っていたpublish/subscribeがなく なってスケールしやすい設計に。

  31. socket.io 0.9のサーバ間共有設計 クライアントの接続データ(handshakeデータ, 所属ルーム等) は、Storeを通して他のサーバへ通知され保存される。 それぞれのサーバが、他サーバに接続しているものを含む全て のクライアントデータ(の一部)を処理し、保持するためスケール しにくい。

  32. 例: socket.io 0.9サーバ10台に、それぞれ1万クライアントが 同時接続する場合。 各サーバそれぞれで10万クライアントのデータが処理・保持さ れる。サーバを増やしても、一つのサーバが処理するデータ数 は変わらず、同時接続数に比例して増える。 サーバA: 10万 サーバB:

    10万 … ※あくまでデータの一部なので全くスケールしないわけではないと思われる。
  33. socket.io 1.0のサーバ間共有設計 各サーバは一切データ共有を行わない。 1.0のAdapterでは、broadcastしたパケットとそのオプション データだけが通知され、サーバには何も保存されないためス ケールしやすい。

  34. 例: socket.io 1.0サーバ10台に、それぞれ1万クライアントが 同時接続する場合。 各サーバは自身に接続している1万クライアントのデータのみを 処理・保持する。サーバを増やすことで、一つのサーバが処理 するデータ数を減らすことができる。 サーバA: 1万 サーバB:

    1万 ...
  35. プロトコル

  36. プロトコルの変化 socket.ioの構成変更や機能追加に伴い、プロトコ ルの大幅な更新が行われた。 • トランスポート層(engine.io)の分離 • バイナリサポート

  37. 0.9のパケット コロン区切りのメッセージ [type] : [id] : [namespace] : [data] 例:

    5::/nsp:{"name":"foo","args":["hi"]}
  38. 0.9のペイロード pollingで一度に複数のパケットを一括送信するの に使用される。\ufffd 区切りでデータ長 +パケットを 連結して繰り返す。 \ufffd [length] \ufffd [packet]

    ... 例: \ufffd32\ufffd5:::{"name":"foo","args":["hi"]}\ufffd ...
  39. 1.0のパケット 区切り文字とJSONのキー名がなくなり効率的に。 データはutf8エンコードされる。 [engine.io type] [type] [namespace] , [data] 例:

    42/nsp,["foo","hi"]
  40. 1.0のバイナリを含むパケット バイナリ数が追加。バイナリデータはプレースホル ダで置き換えられ、別のパケットとして送られる。 [engine.io type] [type] [binary数] - [namespace] ,

    [data] 例: 451-/nsp,["foo",{"_placeholder":true,"num":0}]
  41. 1.0のペイロード バイナリ送信をサポートするtransportでは、文字列も全てバイ ナリに変換される。パケットがutf8エンコードされるのはこのた め。 [0(string), 1(binary)] [length(一桁づつ)] 255 [packet] ...

    例: [00 02 05 ff 34 32 2f 6e 73 70 2c 5b 22 65 63 68 6f 20 62 61 63 6b 22 2c 22 68 69 22 5d]
  42. 1.0のペイロード(文字列) バイナリ送信をサポートしないtransportでは、文字 列のまま送信。バイナリはbase64エンコードされ る。 [length] : [packet] ... 例: 19:42/nsp,["foo","hi"]

  43. プロトコルの変更による影響 socket.io 0.9のプロトコルを実装していた別言語 のsocket.ioサーバ・クライアントは一切使用できな い。 => ユーザが0.9から1.0にアップデートする際の最 も大きな障害になると思われる。

  44. まとめ • engine.ioでより安定したsocket通信ができます。 • middleware便利。モジュールを作って公開しよう。 • バイナリも送受信できるようになった。 • adapterでスケールしやすいアーキテクチャに。 •

    0.9から1.0への移行は難しくない。プロトコルの違いには注 意。
  45. thanks!