152 lines
8.0 KiB
Markdown
152 lines
8.0 KiB
Markdown
# 8.2 WebSocket
|
||
WebSocketはHTML5の重要な特徴です。これはブラウザに基づいたリモートsocketを実現します。ブラウザとサーバが全二重通信することができ、多くのブラウザ(Firefox、Google ChromeとSafari)ではすでにサポートされています。
|
||
|
||
WebSocketが現れる前はリアルタイム通信を実現するために、"ポーリング"とよばれる技術が全面的に採用されていました。すなわち、特定の時間間隔においてブラウザがサーバに対しHTTP Requestを送信し、サーバはリクエストを受け取った後、最新のデータをブラウザに返してリロードします。"ポーリング"ではブラウザがサーバに対して絶え間なくリクエストを送っており、大量の帯域幅を占有します。
|
||
|
||
WebSocketは特殊なパケットヘッダを採用しています。ブラウザとサーバはハンドシェイクの動作のみを必要とするだけで、ブラウザとサーバ間で接続チャンネルを確立することができます。またこの接続では活動状態が保持され、JavaScriptを使用することでコネクションに書き込むことも、中からデータを取り出すこともできます。通常のTCP Scoketを使用するのと同じようなものです。これはWebのリアルタイム化の問題を解決しています。伝統的なHTTPに比べ下のようなメリットがあります:
|
||
|
||
- WebクライアントはTCP接続を確立するだけです
|
||
- Websocketサーバーはデータをwebクライアントにプッシュ(push)できます
|
||
- 軽いヘッダによりデータの転送量を抑えます。
|
||
|
||
WebSocket URLのはじめの入力はws://またはwss://(SSL上で)です。下の図はWebSocketの通信課程を示しています。特定のヘッダを伴ったHTTPハンドシェイクがサーバに送信され、サーバまたはクライアントはJavaScriptを使って何らかのソケット(socket)を使用します。このインターフェースはイベントを通して非同期にデータを受け取ることができます。
|
||
|
||

|
||
|
||
図8.2 WebSocketの原理図
|
||
|
||
## WebSocketの原理
|
||
WebSocketのプロトコルは実に簡単です。はじめのhandshakeが通った後、接続の確立に成功します。この後のデータの通信はすべて"\x00"から始まり、"\xFF"で終わります。クライアントではこれは透明です。WebSocketモジュールは自動的にオリジナルのデータから大事なところを残してあとは取り除いてくれます。
|
||
|
||
ブラウザがWebSocketの接続リクエストを送信すると、サーバはレスポンスを送信します。その後接続の確立に成功します。この過程を通常"ハンドシェイク"(handshaking)と呼びます。下のリクエストとフィードバック情報をご覧ください:
|
||
|
||

|
||
|
||
図8.3 WebSocketのrequestとresponse情報
|
||
|
||
リクエストの"Sec-WebSocket-Key"はランダムです。日々エンコーディングとやりあっているプログラマにはひと目で分かります:これはbase64エンコードが施されたデータで、サーバはこのリクエストを受け取った後この文字列を固定の文字列に連結させる必要があります:
|
||
|
||
258EAFA5-E914-47DA-95CA-C5AB0DC85B11
|
||
|
||
すなわち、`f7cb4ezEAl6C3wRaU6JORA==`を上の固定の文字列に連結し、このような文字列を生成します:
|
||
|
||
f7cb4ezEAl6C3wRaU6JORA==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
|
||
|
||
この文字列に対しまずsha1セキュリティハッシュアルゴリズムを使って2進数の値を計算します。その後base64を使ってこれをエンコードし、ハンドシェイク後の文字列を得ることができます:
|
||
|
||
rE91AJhfC+6JdVcVXOGJEADEJdQ=
|
||
|
||
これをレスポンスヘッダ`Sec-WebSocket-Accept`の値としてクライアントに返します。
|
||
|
||
## GoによるWebSocketの実装
|
||
Go言語の標準パッケージにはWebSocketに対するサポートはありません。しかしオフィシャルでメンテナンスされているgo.netサブパッケージにはこれに対するサポートがあります。以下のようなコマンドによってこのパッケージを取得することができます:
|
||
|
||
go get code.google.com/p/go.net/websocket
|
||
|
||
WebSocketはクライアントとサーバに分けられます。ここでは簡単な例を実現します:ユーザが情報を入力し、クライアントはWebSocketを通じて情報をサーバに送信します。サーバが情報を受け取った後主動的に情報をクライアントにPushします。クライアントは受け取った情報を出力します。クライアントのコードは以下のとおり:
|
||
|
||
<html>
|
||
<head></head>
|
||
<body>
|
||
<script type="text/javascript">
|
||
var sock = null;
|
||
var wsuri = "ws://127.0.0.1:1234";
|
||
|
||
window.onload = function() {
|
||
|
||
console.log("onload");
|
||
|
||
sock = new WebSocket(wsuri);
|
||
|
||
sock.onopen = function() {
|
||
console.log("connected to " + wsuri);
|
||
}
|
||
|
||
sock.onclose = function(e) {
|
||
console.log("connection closed (" + e.code + ")");
|
||
}
|
||
|
||
sock.onmessage = function(e) {
|
||
console.log("message received: " + e.data);
|
||
}
|
||
};
|
||
|
||
function send() {
|
||
var msg = document.getElementById('message').value;
|
||
sock.send(msg);
|
||
};
|
||
</script>
|
||
<h1>WebSocket Echo Test</h1>
|
||
<form>
|
||
<p>
|
||
Message: <input id="message" type="text" value="Hello, world!">
|
||
</p>
|
||
</form>
|
||
<button onclick="send();">Send Message</button>
|
||
</body>
|
||
</html>
|
||
|
||
|
||
クライアントのJSを見ると、WebSocket関数を使って簡単にサーバと接続するsockを作成することができると分かります。ハンドシェイクが成功した後、WebScoketオブジェクトのonopenイベントが呼ばれ、クライアントの接続の確立が成功したことを伝えます。クライアントでは4つのイベントを結びつけています。
|
||
|
||
- 1)onopen 接続を確立すると呼ばれます
|
||
- 2)onmessage 情報を受取ると呼ばれます
|
||
- 3)onerror エラーが発生した時に呼ばれます
|
||
- 4)onclose 接続を閉じた際に呼ばれます
|
||
|
||
我々のサーバの実装は以下の通り:
|
||
|
||
package main
|
||
|
||
import (
|
||
"golang.org/x/net/websocket"
|
||
"fmt"
|
||
"log"
|
||
"net/http"
|
||
)
|
||
|
||
func Echo(ws *websocket.Conn) {
|
||
var err error
|
||
|
||
for {
|
||
var reply string
|
||
|
||
if err = websocket.Message.Receive(ws, &reply); err != nil {
|
||
fmt.Println("Can't receive")
|
||
break
|
||
}
|
||
|
||
fmt.Println("Received back from client: " + reply)
|
||
|
||
msg := "Received: " + reply
|
||
fmt.Println("Sending to client: " + msg)
|
||
|
||
if err = websocket.Message.Send(ws, msg); err != nil {
|
||
fmt.Println("Can't send")
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
func main() {
|
||
http.Handle("/", websocket.Handler(Echo))
|
||
|
||
if err := http.ListenAndServe(":1234", nil); err != nil {
|
||
log.Fatal("ListenAndServe:", err)
|
||
}
|
||
}
|
||
|
||
クライアントがユーザの入力した情報をSendした後、サーバはReceiveを通して対応する情報を受け取ります。その後Sendを通して応答情報を送信します。
|
||
|
||

|
||
|
||
図8.4 WebSocketサーバが受け取った情報
|
||
|
||
上の例でクライアントとサーバでWebSocketを実装するのが非常に簡単だとわかりました。Goのソースコードのnetブランチではすでにこのプロトコルが実装されており、直接持ってきて使用することができます。現在HTML5の発展にしたがって将来WebSocketがWeb開発にとって重要になると考えています。我々はこの方面の知識を蓄える必要があります。
|
||
|
||
|
||
## links
|
||
* [目次](<preface.md>)
|
||
* 前へ: [Socketプログラミング](<08.1.md>)
|
||
* 次へ: [REST](<08.3.md>)
|