websocket を使ってみた - ログ25log25.jp/file/120914.pdf · websocket と ajax との違い...

48
WebSocket を使ってみた pdfver 0.913 (発表直前までに修正をした場合は、pdf 用も後日修正します)

Upload: others

Post on 06-Dec-2019

9 views

Category:

Documents


0 download

TRANSCRIPT

WebSocket

を使ってみた pdf用 ver 0.913

(発表直前までに修正をした場合は、pdf

用も後日修正します)

自己紹介

• @nobi000 (twitter)

– 今回の発表に合わせて取得

[email protected]

• 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.

目次

• 前座 : Ajax の確認

• WebSocket の紹介

• WebSocket を使ったアプリケーション

• WebSocket のプログラミング例

2ch と Gmail / Google maps の違い

• 掲示板(2ch)

–ページを一度開いたら、次のリンクをクリックするまでページは変化しない。

– 「更新」するリンクを押して、初めて新しいページが表示される。

• Gmail / Google maps

–ページを開くだけでなく、マウス操作などによってもページが変化する。

Ajax

• 画面遷移を伴わない、イベントに連動して、バックグラウンド上のhttp通信が可能。

var xmlhttp = new XMLHttpRequest();

var xmlhttp = new ActiveXObject

("Microsoft.XMLHTTP");

WebSocket とは…

• ブラウザ上で双方向のソケット通信を行うための技術・規格。

WebSocket と Ajax との違い

• WebSocket は双方向のリアルタイムな通信が可能

– Ajax では、サーバ側が変化する場合、ポーリング等が必要

• サーバ側も WebSocket に対応 : 必要

–マッシュアップのように、相手側の出力が決まっている場合は置き換えできない。

WebSocket はどのブラウザで使えるの?

• Chrome

• Firefox

• Opera

• Safari

• 最新版であれば、対応

Safari で使えるってことは、

iPad などでも行ける

• iOS 4.2.1 以上で可能

–初代 は、Version UP が必須。

– iPad2 以降はデフォルトで使える。

–ただし WebSocket のバージョンが古いので注意

Android は?

• 標準ブラウザは使えない(はず。未確認)

• Chrome や Firefox をインストールすると使える

Internet Explorer は?

• Internet Explorer 9 までは使えない。

• Internet Explorer 10 で使える(未確認)

目次

• 前座 : Ajax の確認

• WebSocket の紹介

• WebSocket を使ったアプリケーション

• WebSocket のプログラミング例

WebSocket でできること?

• チャット

• 通信対戦

• 表示画面(ブラウザ上)の共有

• モニタリング

• リモートデスクトップ

• センサー管理

ここでデモを紹介します

目次

• 前座 : Ajax の確認

• WebSocket の紹介

• WebSocket を使ったアプリケーション

• 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

WebSocket を使う際のステップ

1. コネクションの確立

2. データの送受信

3. コネクションの切断

WebSocket を使う際のステップ

1. コネクションの確立

2. データの送受信

3. コネクションの切断

WebSocket を使う際のステップ

1. コネクションの確立

2. データの送受信

3. コネクションの切断

(これはどちらからでも可能)

クライアント(ブラウザ側)

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

ご清聴ありがとうございました

今日の発表のpdf

http://log25.jp/

以下、追加資料

Accept フィールドの生成

サーバ側

完全にソケットプログラミング

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