157 lines
5.8 KiB
Markdown
157 lines
5.8 KiB
Markdown
# 8.2 WebSocket
|
||
WebSocket是HTML5的重要特性,它實現了基於瀏覽器的遠端socket,它使瀏覽器和伺服器可以進行全雙工通訊,許多瀏覽器(Firefox、Google Chrome和Safari)都已對此做了支援。
|
||
|
||
在WebSocket出現之前,為了實現即時通訊,採用的技術都是“輪詢”,即在特定的時間間隔內,由瀏覽器對伺服器發出HTTP Request,伺服器在收到請求後,返回最新的資料給瀏覽器重新整理,“輪詢”使得瀏覽器需要對伺服器不斷髮出請求,這樣會佔用大量頻寬。
|
||
|
||
WebSocket採用了一些特殊的報頭,使得瀏覽器和伺服器只需要做一個握手的動作,就可以在瀏覽器和伺服器之間建立一條連線通道。且此連線會保持在活動狀態,你可以使用JavaScript來向連線寫入或從中接收資料,就像在使用一個常規的TCP Socket一樣。它解決了Web即時化的問題,相比傳統HTTP有如下好處:
|
||
|
||
- 一個Web客戶端只建立一個TCP連線
|
||
- Websocket伺服器端可以推送(push)資料到web客戶端.
|
||
- 有更加輕量級的頭,減少資料傳送量
|
||
|
||
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安全雜湊演算法計算出二進位制的值,然後用base64對其進行編碼,即可以得到握手後的字串:
|
||
|
||
rE91AJhfC+6JdVcVXOGJEADEJdQ=
|
||
|
||
將之作為回應標頭`Sec-WebSocket-Accept`的值反饋給客戶端。
|
||
|
||
## Go實現WebSocket
|
||
Go語言標準套件裡面沒有提供對WebSocket的支援,但是在由官方維護的go.net子套件中有對這個的支援,你可以透過如下的命令取得該套件:
|
||
|
||
go get golang.org/x/net/websocket
|
||
|
||
WebSocket分為客戶端和伺服器端,接下來我們將實現一個簡單的例子:使用者輸入資訊,客戶端透過WebSocket將資訊傳送給伺服器端,伺服器端收到資訊之後主動Push資訊到客戶端,然後客戶端將輸出其收到的資訊,客戶端的程式碼如下:
|
||
|
||
```html
|
||
|
||
<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事件,告訴客戶端連線已經成功建立。客戶端一共綁定了四個事件。
|
||
|
||
- 1)onopen 建立連線後觸發
|
||
- 2)onmessage 收到訊息後觸發
|
||
- 3)onerror 發生錯誤時觸發
|
||
- 4)onclose 關閉連線時觸發
|
||
|
||
我們伺服器端的實現如下:
|
||
|
||
```Go
|
||
|
||
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>)
|