websocket を使ってみた - ログ25log25.jp/file/120914.pdf · websocket と ajax との違い...
TRANSCRIPT
自己紹介
• @nobi000 (twitter)
– 今回の発表に合わせて取得
• http://log25.jp/
– 今日の発表のpdfも上記から
• nobi (niconico)
– ニコニコ動画の5年間の歩みを分析してみた(sm 17165490) 他、sm17738048
– 第2回ニコニコ学会βポスター発表「ニコニコ動画のタグを分析してみた」
WebSocket の位置づけ
広義の HTML 5
WebSocket
WebStorage
WebWorker
Indexed Database
WHATWG のHTML5
Canvas 2D
Macrodata
W3C の HTML5
Elements
Froms
Offline Events
Drag & Drop API
etc.
2ch と Gmail / Google maps の違い
• 掲示板(2ch)
–ページを一度開いたら、次のリンクをクリックするまでページは変化しない。
– 「更新」するリンクを押して、初めて新しいページが表示される。
• Gmail / Google maps
–ページを開くだけでなく、マウス操作などによってもページが変化する。
Ajax
• 画面遷移を伴わない、イベントに連動して、バックグラウンド上のhttp通信が可能。
var xmlhttp = new XMLHttpRequest();
var xmlhttp = new ActiveXObject
("Microsoft.XMLHTTP");
WebSocket と Ajax との違い
• WebSocket は双方向のリアルタイムな通信が可能
– Ajax では、サーバ側が変化する場合、ポーリング等が必要
• サーバ側も WebSocket に対応 : 必要
–マッシュアップのように、相手側の出力が決まっている場合は置き換えできない。
Safari で使えるってことは、
iPad などでも行ける
• iOS 4.2.1 以上で可能
–初代 は、Version UP が必須。
– iPad2 以降はデフォルトで使える。
–ただし WebSocket のバージョンが古いので注意
WebSocket のバージョン
chrome Firefox Opera Safari
Hixie-75 4 5.0.0
Hixie-76
Hybi-00
6 (11.00) 5.0.1
Hybi-07 6
Hybi-10 14 7
Hybi-17
RFC6455
16 11 6
•主要なバージョンのみ紹介
•括弧付きはデフォルトでoff
クライアント(ブラウザ側)
JavaScript フルスクラッチの場合
1. コネクションの確立
– var ws = new
WebSocket("ws://localhost:8000/");
2. サーバ側に送信
– ws.send("送信文字列");
3. コネクションの切断
– ws.close();
クライアント(ブラウザ側)
JavaScript フルスクラッチの場合
イベント イベント内容
open コネクションが確立され、データの送受信が可能になった時
message サーバからデータを受信した時
引数を取る(eventとする)。
event.data が受信したデータ
error エラーが発生した時
close サーバ側から、コネクションの解放要求が来た時
ブラウザ側のライブラリ
• Socket.io
– WebSocket が無い環境では、他で代替して、通信する(Canvas における Explorer Canvas)
• jQuery のプラグイン
– var ws = $.websocket("ws://localhost:8080/");
サーバ側
完全にソケットプログラミング
1. コネクションの確立
ハンドシェイクが必要。
GET / HTTP/1.1 Upgrade: websocket Connection: Upgrade Host: 127.0.0.1:8000 Origin: http://localhost Sec-WebSocket-Key: OLdsFUdouiPqrn7pfj1hkg== Sec-WebSocket-Version: 13 Sec-WebSocket-Extensions: x-webkit-deflate-frame
var ws = new
WebSocket("…");
サーバ側
完全にソケットプログラミング
1. コネクションの確立
ハンドシェイクが必要。
このやり取りを行うと、WebSocket が利用可能
HTTP/1.1 101 Switching Protocols Upgrade: WebSocket Connection: Upgrade Sec-WebSocket-Accept: XeA3VARVsdC9imRoy3E7RMarxBA=
open イベント発生
サーバ側
完全にソケットプログラミング
2. サーバ側に送信
3. コネクションの切断
以下のデータフレームをやり取りする
F
I
N
R
S
V
1
R
S
V
2
R
S
V
3
opcode M
A
S
K
Payload 長 延長 Payload 長(payload == 126:16bit
or 127:64bit)
延長 Payload 長
延長 Payload 長 Masking - key(Mask が 1 の時)
Masking - key 以下、データ
データ
サーバ側のライブラリ
• Jetty , jwebsocket (Java)
• libwebsockets (C)
• Node.js + Socket.io (JavaScript)
• PocketIO (perl)
• pywebsocket (python)
• php-websocket (php)
終わり
• WebSocket を使うと、いろんなアプリケーションが開発できる
• 他の技術との組み合わせに期待
+ WebGL で、MMORPG!
+ WebRTC で、部屋遠隔管理!
• 各種ロゴについては、無断で使用いたしました。
参考にさせていただきました
• 小松健作:徹底解説 HTML5 APIガイドブック コミュニケーション系API編(秀和システム)
• http://kuruman.org/diary/2011/05/28/file/websocket_study_kuruma.pdf
• https://github.com/lemmingzshadow/php-websocket
• http://d.hatena.ne.jp/Jxck/20120725/1343174392
• http://d.hatena.ne.jp/gtk2k/20120203
サーバ側
完全にソケットプログラミング
1. コネクションの確立
• Acceptフィールドの生成
① Sec-WebSocket-Keyフィールドと '258EAFA5-
E914-47DA-95CA-C5AB0DC85B11'の文字列連結
下は PHP での実装例
base64_encode(pack('H*', sha1($headers['Sec-WebSocket-Key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
サーバ側
完全にソケットプログラミング
1. コネクションの確立
• Acceptフィールドの生成
②その結果をSHA1ハッシュで計算
③文字列をバイナリ文字列化
base64_encode(pack('H*', sha1($headers['Sec-WebSocket-Key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
サーバ側
完全にソケットプログラミング
1. コネクションの確立
• Acceptフィールドの生成
④ Base64で変換
base64_encode(pack('H*', sha1($headers['Sec-WebSocket-Key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
データフレームの解釈
F
I
N
R
S
V
1
R
S
V
2
R
S
V
3
opcode M
A
S
K
Payload 長 延長 Payload 長(payload == 126:16bit
or 127:64bit)
延長 Payload 長
延長 Payload 長 Masking - key(Mask が 1 の時)
Masking - key 以下、データ
データ
データフレームの解釈
• opcode(4bit):データの解釈
0x1: テキストデータ、0x2: バイナリデータ
0x8: close、0x9: ping
F
I
N
R
S
V
1
R
S
V
2
R
S
V
3
opcode M
A
S
K
Payload 長 延長 Payload 長(payload == 126:16bit
or 127:64bit)
延長 Payload 長
延長 Payload 長 Masking - key(Mask が 1 の時)
Masking - key 以下、データ
データ
データフレームの解釈
• Opcode(4bit):データの解釈
0x1: テキストデータ、0x2: バイナリデータ
0x8: close、0x9: ping
opcode の取得例(php)
$firstByteBinary = sprintf('%08b', ord($data[0])); $opcode = bindec(substr($firstByteBinary, 4, 4));
データフレームの解釈
• mask(1bit): データのマスクを行っているか。1ならmasking-key によってマスクがかかっている
• ブラウザ -> サーバはマスクを必ず掛ける
• サーバ -> ブラウザはマスクを掛けてはいけない
F
I
N
R
S
V
1
R
S
V
2
R
S
V
3
opcode M
A
S
K
Payload 長 延長 Payload 長(payload == 126:16bit
or 127:64bit)
延長 Payload 長
延長 Payload 長 Masking - key(Mask が 1 の時)
Masking - key 以下、データ
データ
データフレームの解釈
• mask: データのマスクを行っているか。1ならmasking-key によってマスクがかかっている
• mask bitの取得例(php)
• マスクの解除例(php)
$payloadOffset はデータ開始バイト
$secondByteBinary = sprintf('%08b',ord($data[1])); $isMasked = ($secondByteBinary[0] == '1') ? true : false;
$unmaskedPayload .= $data[$i] ^ $mask[($i - $payloadOffset) % 4];
データフレームの解釈
• Payload 長(7bit): データの長さ
• 126:延長payload 長(16bit)がデータの長さ
• 127:延長payload 長(64bit)がデータの長さ
F
I
N
R
S
V
1
R
S
V
2
R
S
V
3
opcode M
A
S
K
Payload 長 延長 Payload 長(payload == 126:16bit
or 127:64bit)
延長 Payload 長
延長 Payload 長 Masking - key(Mask が 1 の時)
Masking - key 以下、データ
データ
データフレームの解釈
• Payload 長(7bit): データの長さ
• 126:延長payload 長(16bit)がデータの長さ
• 127:延長payload 長(64bit)がデータの長さ
$payloadLength = ord($data[1]) & 127; if($payloadLength === 126){ $dataLength = bindec(sprintf('%08b', ord($data[2])) . sprintf('%08b', ord($data[3]))); }else if($payloadLength === 127){ $tmp = ''; for($i = 0; $i < 8; $i++){ $tmp .= sprintf('%08b', ord($data[$i+2])); } $dataLength = bindec($tmp); }
データフレームへの変換
• opcode(4bit):データの解釈
0x1: テキストデータ、0x2: バイナリデータ
0x8: close、0x9: ping
F
I
N
R
S
V
1
R
S
V
2
R
S
V
3
opcode M
A
S
K
Payload 長 延長 Payload 長(payload == 126:16bit
or 127:64bit)
延長 Payload 長
延長 Payload 長 Masking - key(Mask が 1 の時)
Masking - key 以下、データ
データ
// 後続フレームなし、テキストデータの場合 10000001 $frameHead[0] = 129;
データフレームへの変換
• Payload 長(7bit): データの長さ
• 126:延長payload 長(16bit)がデータの長さ
• 127:延長payload 長(64bit)がデータの長さ
F
I
N
R
S
V
1
R
S
V
2
R
S
V
3
opcode M
A
S
K
Payload 長 延長 Payload 長(payload == 126:16bit
or 127:64bit)
延長 Payload 長
延長 Payload 長 Masking - key(Mask が 1 の時)
Masking - key 以下、データ
データ
データフレームへの変換
• Payload 長(7bit): データの長さ
• 126:延長payload 長(16bit)がデータの長さ
• 127:延長payload 長(64bit)がデータの長さ
$payloadLength = strlen($payload); if($payloadLength > 65535){ $payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8); $frameHead[1] = ($masked === true) ? 255 : 127; for($i = 0; $i < 8; $i++){ $frameHead[$i+2] = bindec($payloadLengthBin[$i]); } }elseif($payloadLength > 125){ $payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8); $frameHead[1] = ($masked === true) ? 254 : 126; $frameHead[2] = bindec($payloadLengthBin[0]); $frameHead[3] = bindec($payloadLengthBin[1]); }