Socket.IOとGoの話
goでsocket.ioのclientを実装した話です.
背景
socket.io を使ったアプリケーションに負荷試験をしたいというのがありました.
go で書きたいという気持ちとよく考えたら socket.io のことあまり知らないなという気持ちから実装してみようと思い始めたものです.
はじめに
Socket.IOとは
FEATURING THE FASTEST AND MOST RELIABLE REAL-TIME ENGINE
を謳っているリアルタイム双方向event-basedなエンジンです.
Node.jsで実装されており, キラーアプリとしてNode.jsの普及に貢献したイメージがあります. 間違っていたらすいません.
Socket.IOは現在version 2.0まで出ており, 今回話すのもversion 2.0の話です.
よくある誤解というかあまり意識せずに Socket.IO == WebSocket
という人もいるように感じます.
実際には Socket.IO !== WebSocket
です.
むしろ Socket.IOはWebSocketの対応状況が悪かった時期に同様のことをするためのものだったという理解です.
Socket.IOの中ではTransportの部分は抽象化されており, その部分はEngine.IOと呼ばれています, 必ずしもWebSocketではありません.
Engine.IOはかなり簡素化されたRealtime Engineでeventなどの概念は存在しません. Engine.IOの仕様として
- WebSocket
- XMLHttpRequest
- JSONP
の3つのTransportをサポートしなければいけません. Engine.IOを通すことで上の層ではこれらを意識しなくて良くなります.
別に今の時代 WebSocketでいいだろと思った方は以下の記事にWebSocketを使う上で考えなければいけないことがまとまっているので読んでみてください.
インターネットは難しく, リアルタイム通信ができないと価値を提供できないものの場合はWebSocketだけではなくfallbackを考えなければいけず, それが実現されているSocket.IOを使うという選択肢はあると思います.
Engine.IO
Engine.IOプロトコルに関しては以下のリポジトリにまとまっています.
最新はrevision 3でrevision 3の話をします. 見てみるとわかるのですがテキストベースのすごくシンプルなものになっています.
<packet type id>[<data>]
という形式でpacket type idも7種類しかありません.
- open
- close
- ping
- pong
- message
- upgrade
- noop
接続の維持, 識別, データの送信などの基本的な操作しかない.
非常に簡単で人間が手でも書くことができる. 上のスライドには載せてあるが実際に手でうって確認することができるリポジトリがあるので興味があればそちらを使ってみてください.
Socket.IO
Socket.IOのプロトコルについては以下のリポジトリにまとまっています.
今はrevision 4でrevision 4の話をします.
Engine.IOのmessageのpayload部がSocket.IOのPacketになります.
Packetは7種類しかなく基本的にテキストベースで非常にシンプルなプロトコルであることがわかります.
- CONNECT
- DISCONNECT
- EVENT
- ACK
- ERROR
- BINARY_EVENT
- BINARY_ACK
です. 一応バイナリも対応しています.
上のリポジトリだけ見るとどういった形式で格納されているかは触れられていないので実際に使われているパーサーの実装を読みましょう.
実装を見てみると以下の形式であることがわかります.
<packet type id>[(attachments)-][(namespace:/),][id][data]
シンプルですがEngine.IOと比べるとnamespaceなどの概念が追加されています.
実際に流れるのが以下のようなテキスト列になります.
先頭の記号は通信の向きなので実際には送られないものです. // 以降はコメントで e:はEngine.IOのpacket type, s:はSocket.IOのpacket typeになっています.
> 0 // e:open > 40 // e:message s:connect < 42[“message”,”hello”] // e:message s:event data > 42[“reply”,”?hello”] // e:message s:event data < 41 // e:message s:disconnect < 42[“message”,”hello”] // e:message s:event data < 40 // e:message s:connect < 42[“message”,”hello”] // e:message s:event data > 42[“reply”,”?hello”] // e:message s:event data
こういったテキストを送れる様になるだけでSocket.IOをしゃべることができます.
これをgoで実装したライブラリが以下です.
基本的にはこういう感じでパケットを組み立てるだけのライブラリなのですが, Engine.IOのレイヤーでping/pongをしてくれます.
このライブラリから普通にNode.jsのSocket.IOとも話すことができます.
実はこのライブラリ, 途中で力尽きており完全に仕様を網羅しているわけではないのでご了承いただければと思います.
終わりに
当初の負荷試験がしたいという目的は忘れてYak shavingに励んでいました.
でも楽しかったので問題なしです.