Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

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

Naoyuki Kanezawa

June 23, 2014
Tweet

More Decks by Naoyuki Kanezawa

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

  4. Engine.IO と Upgrade

    View Slide

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

    View Slide

  6. fallback
    1. 最初にwebsocketで接続を試みる。
    2. 接続できたら終了。
    3. 接続できなかった場合、初期設定で最低10秒間タイムアウ
    トを待つ。
    4. タイムアウトしたら、改めてpollingで接続を行う。
    => websocketが使えない場合の初回接続速度に問題があっ
    た。

    View Slide

  7. upgrade
    1. httpのgetリクエストを行い、pollingの接続を確立させる。
    2. upgradeできるか判定。設定次第でupgradeは行われず終
    了。
    3. pollingを維持したまま、並列にwebsocketで通信しパケット
    の交換ができるかどうかまで試す(probe)。
    4. websocketがつながったらpollingが止まるのを待ち(メッ
    セージのロスを防ぐため)、メインのトランスポートを切り替
    える。

    View Slide

  8. サポートされるトランスポート
    ● websocket
    ● polling
    ○ xhr
    ○ jsonp
    ※polling時はxhrが優先的に使用される。

    View Slide

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

    View Slide

  10. upgrade関連オプション(client側)
    transports: [‘polling’, ‘websocket’]
    使用するトランスポートを順番に設定する。[‘websocket’] と
    すると最初からwebsocketで接続することもできる。
    rememberUpgrade: false
    一度upgradeに成功した後に再接続すると、upgradeプロセ
    スを飛ばしてwebsocketで接続する。SSLの時などに有効
    にするとよいらしい。

    View Slide

  11. Middleware

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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);
    });

    View Slide

  17. ネームスペース
    middlewareはネームスペース単位。
    // デフォルトネームスペース。次の二つは同じ意味。
    io.use(function(socket, next) {});
    io.of(‘/’).use(function(socket, next) {});
    // foo ネームスペース
    io.of(‘/foo’).use(function(socket, next) {});

    View Slide

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

    View Slide

  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();
    });

    View Slide

  20. バイナリサポート

    View Slide

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

    View Slide

  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]));
    });

    View Slide

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

    View Slide

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

    View Slide

  25. バイナリ送信できるトランスポート
    ● ◯ websocket
    ● ◯ XMLHttpRequest Level 2
    ● ☓ 古い仕様のwebsocket
    ● ☓ XMLHttpRequest
    ● ☓ JSONP
    ※バイナリ送信をサポートしないトランスポートでも、base64エンコードされた文字列
    として透過的にデータを送受信できる。

    View Slide

  26. バイナリの扱いに注意
    データサイズに制限はないが、メモリへ読み込むので、大きな
    ファイルの配信などには向かない。
    この問題を解決するために、別モジュールによるstream送信の
    サポートが計画されている。

    View Slide

  27. Adapter

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    ※あくまでデータの一部なので全くスケールしないわけではないと思われる。

    View Slide

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

    View Slide

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

    View Slide

  35. プロトコル

    View Slide

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

    View Slide

  37. 0.9のパケット
    コロン区切りのメッセージ
    [type] : [id] : [namespace] : [data]
    例: 5::/nsp:{"name":"foo","args":["hi"]}

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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]

    View Slide

  42. 1.0のペイロード(文字列)
    バイナリ送信をサポートしないtransportでは、文字
    列のまま送信。バイナリはbase64エンコードされ
    る。
    [length] : [packet] ...
    例: 19:42/nsp,["foo","hi"]

    View Slide

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

    View Slide

  44. まとめ
    ● engine.ioでより安定したsocket通信ができます。
    ● middleware便利。モジュールを作って公開しよう。
    ● バイナリも送受信できるようになった。
    ● adapterでスケールしやすいアーキテクチャに。
    ● 0.9から1.0への移行は難しくない。プロトコルの違いには注
    意。

    View Slide

  45. thanks!

    View Slide