這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
本文同步至 http://www.waylau.com/go-websocket-chat/
這個應用程式範例展示了如何使用 WebSocket, Golang 和 jQuery 建立一個簡單的web聊天應用程式。這個樣本的原始碼在 https://github.com/waylau/goChat 。
Running the example 運行樣本
這個樣本需要 Golang 開發環境。 該頁面描述如何安裝開發環境。
一旦你去啟動和運行,您可以下載、構建和啟動並執行例子, 使用命令:
go get gary.burd.info/go-websocket-chatgo-websocket-chat
在支援 websocket 的瀏覽器嘗試開啟 http://127.0.0.1:8080/ 啟動應用
Server 伺服器
伺服器程式實現了 http 包,包含了 Go 分發和 Gorilla 項目的 websocket 包.
應用程式定義了兩種類型, connection 和 hub 。伺服器為每個 webscocket 串連 建立的一個 connection 類型的執行個體 。 連接器扮演了 websocket 和 hub 類型單例 之間的媒介 。 hub 保持一組註冊了的連接器 和 廣播到連接器的資訊。
程式運行了一個 goroutine 給 hub 和兩個 goroutine 給每個連接器。 goroutine 通過 channel 和其他進行交流。 hub 擁有註冊連接器、登出連接器和廣播資訊的 channel。一個連機器擁有緩衝的發出資訊的 channel 。其中一個 連接器的 goroutine 從這個 channel 中讀資訊 並把資訊寫入 webscoket。另外一個連接器 goroutine 從 websocket 讀資訊,並把資訊發送到 hub。
下面是 hub 類型代碼:
package maintype hub struct { // 註冊了的連接器 connections map[*connection]bool // 從連接器中發入的資訊 broadcast chan []byte // 從連接器中註冊請求 register chan *connection // 從連接器中登出請求 unregister chan *connection}var h = hub{ broadcast: make(chan []byte), register: make(chan *connection), unregister: make(chan *connection), connections: make(map[*connection]bool),}func (h *hub) run() { for { select { case c := <-h.register: h.connections[c] = true case c := <-h.unregister: if _, ok := h.connections[c]; ok { delete(h.connections, c) close(c.send) } case m := <-h.broadcast: for c := range h.connections { select { case c.send <- m: default: delete(h.connections, c) close(c.send) } } } }}
應用程式的 主要 函數啟動 hub 以 goroutine 形式運行方法。連接器 發送請求到 hub 通過 註冊、登出和廣播 channel。
hub 註冊連接器通過添加 connection 的指標作為 connections map 的主鍵。這個 map 的值通常是 true。
登出的代碼有點複雜。除了從 connections map 刪除連接器的指標外, hub 關閉了 connection 的發送,來標識沒有資訊再被發送到 connection了。
hub 通過迴圈註冊連接器和發送資訊到連接器的發送 channel 來控制資訊。 如果連接器的發送緩衝區已經滿了,那麼 hub 假設 用戶端已死或卡住了。這種情況下, hub 登出連接器 並關閉 websocket.
下面關於 connection 類型的代碼:
package mainimport ( "github.com/gorilla/websocket" "net/http")type connection struct { // websocket 連接器 ws *websocket.Conn // 發送資訊的緩衝 channel send chan []byte}func (c *connection) reader() { for { _, message, err := c.ws.ReadMessage() if err != nil { break } h.broadcast <- message } c.ws.Close()}func (c *connection) writer() { for message := range c.send { err := c.ws.WriteMessage(websocket.TextMessage, message) if err != nil { break } } c.ws.Close()}var upgrader = &websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}func wsHandler(w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { return } c := &connection{send: make(chan []byte, 256), ws: ws} h.register <- c defer func() { h.unregister <- c }() go c.writer() c.reader()}
wsHandler 方法被主函數當做http handler註冊。HTTP 串連到 WebSocket 通訊協定的升級,建立一個連線物件,註冊這個串連到 sub ,並通過 defer延遲語句 來控制 串連的登出。
接著,wsHandler 方法開啟 連接器的寫入方法作為一個 goroutine。 寫入方法將資訊從連接器的 channel 轉入 websocket。當 hub 關閉 channel 或者 在寫入 websocket 時出錯,寫入方法關閉。
最後,wsHandler 方法 調用連接器的 讀 方法。 讀方法將 入站訊息 從 websocket 轉到 hub。
這裡是伺服器的代碼的其餘部分:
package mainimport ( "flag" "go/build" "log" "net/http" "path/filepath" "text/template")var ( addr = flag.String("addr", ":8080", "http service address") assets = flag.String("assets", defaultAssetPath(), "path to assets") homeTempl *template.Template)func defaultAssetPath() string { p, err := build.Default.Import("gary.burd.info/go-websocket-chat", "", build.FindOnly) if err != nil { return "." } return p.Dir}func homeHandler(c http.ResponseWriter, req *http.Request) { homeTempl.Execute(c, req.Host)}func main() { flag.Parse() homeTempl = template.Must(template.ParseFiles(filepath.Join(*assets, "home.html"))) go h.run() http.HandleFunc("/", homeHandler) http.HandleFunc("/ws", wsHandler) if err := http.ListenAndServe(*addr, nil); err != nil { log.Fatal("ListenAndServe:", err) }}
應用主程式啟動 hub goroutine。 接著 主程式 註冊 首頁 和 websocket 連接器的控制器N。最後主程式啟動 HTTP 伺服器。
Client 用戶端
用戶端的實現是一個簡單的 HTML 檔案:
<html><head><title>Chat Example</title><script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script><script type="text/javascript"> $(function() { var conn; var msg = $("#msg"); var log = $("#log"); function appendLog(msg) { var d = log[0] var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight; msg.appendTo(log) if (doScroll) { d.scrollTop = d.scrollHeight - d.clientHeight; } } $("#form").submit(function() { if (!conn) { return false; } if (!msg.val()) { return false; } conn.send(msg.val()); msg.val(""); return false }); if (window["WebSocket"]) { conn = new WebSocket("ws://{{$}}/ws"); conn.onclose = function(evt) { appendLog($("<div><b>Connection closed.</b></div>")) } conn.onmessage = function(evt) { appendLog($("<div/>").text(evt.data)) } } else { appendLog($("<div><b>Your browser does not support WebSockets.</b></div>")) } });</script><style type="text/css">html { overflow: hidden;}body { overflow: hidden; padding: 0; margin: 0; width: 100%; height: 100%; background: gray;}#log { background: white; margin: 0; padding: 0.5em 0.5em 0.5em 0.5em; position: absolute; top: 0.5em; left: 0.5em; right: 0.5em; bottom: 3em; overflow: auto;}#form { padding: 0 0.5em 0 0.5em; margin: 0; position: absolute; bottom: 1em; left: 0px; width: 100%; overflow: hidden;}</style></head><body><div id="log"></div><form id="form"> <input type="submit" value="Send" /> <input type="text" id="msg" size="64"/></form></body></html>
用戶端使用 jQuery
文檔載入。指令碼檢查 websocket 的功能 。如果 WebSocket 功能 可以用,然後開啟指令碼與伺服器的串連,並註冊一個回調處理來自伺服器的資訊。回調使用 appendlog 方法將訊息添加到聊天記錄。
appendlog 方法檢查在添加新的內容時的滾動位置,從而可以讓使用者手動滾動聊天記錄而不會被新來的訊息中斷。如果聊天記錄滾動至底部,那麼新內容添加的到舊內容的後面。否則,滾動的位置不會改變。
表單一處理器將使用者的輸入寫入到 WebSocket 並且清除輸入欄位。
參考:http://gary.burd.info/go-websocket-chat