# 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 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事件,告訴客戶端連接已經成功建立。客戶端一共綁定了四個事件。
- 1)onopen 建立連接后觸發
- 2)onmessage 收到消息后觸發
- 3)onerror 發生錯誤時觸發
- 4)onclose 關閉連接時觸發
我們服務器端的實現如下:
package main
import (
"code.google.com/p/go.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>)
- 目錄
- Go環境配置
- Go安裝
- GOPATH 與工作空間
- Go 命令
- Go開發工具
- 小結
- Go語言基礎
- 你好,Go
- Go基礎
- 流程和函數
- struct
- 面向對象
- interface
- 并發
- 小結
- Web基礎
- web工作方式
- Go搭建一個簡單的web服務
- Go如何使得web工作
- Go的http包詳解
- 小結
- 表單
- 處理表單的輸入
- 驗證表單的輸入
- 預防跨站腳本
- 防止多次遞交表單
- 處理文件上傳
- 小結
- 訪問數據庫
- database/sql接口
- 使用MySQL數據庫
- 使用SQLite數據庫
- 使用PostgreSQL數據庫
- 使用beedb庫進行ORM開發
- NOSQL數據庫操作
- 小結
- session和數據存儲
- session和cookie
- Go如何使用session
- session存儲
- 預防session劫持
- 小結
- 文本文件處理
- XML處理
- JSON處理
- 正則處理
- 模板處理
- 文件操作
- 字符串處理
- 小結
- Web服務
- Socket編程
- WebSocket
- REST
- RPC
- 小結
- 安全與加密
- 預防CSRF攻擊
- 確保輸入過濾
- 避免XSS攻擊
- 避免SQL注入
- 存儲密碼
- 加密和解密數據
- 小結
- 國際化和本地化
- 設置默認地區
- 本地化資源
- 國際化站點
- 小結
- 錯誤處理,調試和測試
- 錯誤處理
- 使用GDB調試
- Go怎么寫測試用例
- 小結
- 部署與維護
- 應用日志
- 網站錯誤處理
- 應用部署
- 備份和恢復
- 小結
- 如何設計一個Web框架
- 項目規劃
- 自定義路由器設計
- controller設計
- 日志和配置設計
- 實現博客的增刪改
- 小結
- 擴展Web框架
- 靜態文件支持
- Session支持
- 表單支持
- 用戶認證
- 多語言支持
- pprof支持
- 小結
- 參考資料