Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Engine.IO と Upgrade

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Middleware

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

// クライアント 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(); });

Slide 20

Slide 20 text

バイナリサポート

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Adapter

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

例: socket.io 0.9サーバ10台に、それぞれ1万クライアントが 同時接続する場合。 各サーバそれぞれで10万クライアントのデータが処理・保持さ れる。サーバを増やしても、一つのサーバが処理するデータ数 は変わらず、同時接続数に比例して増える。 サーバA: 10万 サーバB: 10万 … ※あくまでデータの一部なので全くスケールしないわけではないと思われる。

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

プロトコル

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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]

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

thanks!