使用 Go 語言和 HTML5 WebSocket 構建一個 Web 聊天室
這個應用演示如何使用 Google Go 語言和 HTML5 的 WebSocket 來實現一個簡單的基于 Web 的聊天程序。
下圖是聊天應用的截圖:
你可輸入 email 來加入聊天室,我們將從 Gravatar 上獲取對應的用戶名和頭像,當你正在聊天時,你能在界面右側看到聊天室其他人的姓名和頭像。你可以輸入信息來跟他們聊天。
現在,讓我們來看看如何實現這么一個程序。
服務器端
首先我們需要一個名為 ActiveRoom 聊天室引擎作為整個應用的核心。該引擎將在下面的代碼中定義,當程序主函數啟動時將會初始化一個聊天室引擎實例并作為一個全局變量。
正在運行的實例用于維護所有 websocket 連接并處理收到的消息。一旦通過廣播渠道接收到一個新的消息,它會將該消息發送到所有連接發送渠道。
Message 是服務器和客戶端數據交互的基本數據類型,在這里我們定義了兩個消息類型,一個是文本消息,另外一個是實時的用戶在線狀態。
type ActiveRoom struct { OnlineUsers map[string]*OnlineUser Broadcast chan Message CloseSign chan bool } type Message struct { MType string TextMessage TextMessage UserStatus UserStatus } func (this *ActiveRoom) run() { for { select { case b := <-this.Broadcast: for _, online := range this.OnlineUsers { online.Send <- b } case c := <-this.CloseSign: if c == true { close(this.Broadcast) close(this.CloseSign) return } } } }
OnlineUser 類代表一個成功連接到聊天室的用戶,它維護著 ActiveRoom 實例的指針、服務器和客戶端之間的 websocket 連接,同時包括正在聊天的用戶和 Go 的通訊渠道。
OnlineUser 定義了兩個指針方法
PushToClient:
從發送渠道讀取消息然后將這些消息通過 websocket 推送給客戶端。
PullFromClient:
從客戶端讀取消息并發送到正在運行的 ActiveRoom 實例。
這兩個方法使用 "for" 語句來等待新的消息,除非 websocket 連接中斷。
type OnlineUser struct { InRoom *ActiveRoom Connection *websocket.Conn UserInfo *User Send chan Message } func (this *OnlineUser) PullFromClient() { for { var content string err := websocket.Message.Receive(this.Connection, &content) if err != nil { return } m := Message{ MType: TEXT_MTYPE, TextMessage: TextMessage{ UserInfo: this.UserInfo, Time: humanCreatedAt(), Content: content, }, } this.InRoom.Broadcast <- m } } func (this *OnlineUser) PushToClient() { for b := range this.Send { err := websocket.JSON.Send(this.Connection, b) if err != nil { break } } }
下面我們來看看程序流程,BuildConnection
函數在 main 函數中被注冊為 websocket 連接的處理器:
http.Handle("/chat", websocket.Handler(wscon.BuildConnection))
當有一個 websocket 連接請求,該函數將做一些初始化工作用于處理新的連接:
func BuildConnection(ws *websocket.Conn) { email := ws.Request().URL.Query().Get("email") onlineUser := &OnlineUser{ InRoom: runningActiveRoom, Connection: ws, Send: make(chan Message, 256), UserInfo: &User{ Email: email, Name: strings.Split(email, "@")[0], Gravatar: libs.UrlSize(email, 20), }, } runningActiveRoom.OnlineUsers[email] = onlineUser m := Message{ MType: STATUS_MTYPE, UserStatus: UserStatus{ Users: runningActiveRoom.GetOnlineUsers(), }, } runningActiveRoom.Broadcast <- m go onlineUser.PushToClient() onlineUser.PullFromClient() onlineUser.killUserResource() }
客戶端
最后一部分是客戶端的實現,這是采用 JavaScript 實現的。它打開了一個新的 websocket 連接到聊天服務器,并注冊回調函數用于處理來自服務器端的消息。你會發現當連接收到新的消息時,conn.onmessage 將被調用。現在你只需將接收到的消息交給對應的 JavaScript 函數去處理:
if (window["WebSocket"]) { conn = new WebSocket("ws://{{.WebSocketHost}}/chat?email={{.Email}}"); conn.onopen = function() {}; conn.onmessage = function(evt) { var data = JSON.parse(evt.data); switch(data.MType) { case "text_mtype": addMessage(data.TextMessage) break; case "status_mtype": updateUsers(data.UserStatus) break; default: } }; conn.onerror = function() { errorMessage("<strong> An error just occured.<strong>") }; conn.onclose = function() { errorMessage("<strong>Connection closed.<strong>") }; } else { errorMessage("Your browser does not support WebSockets."); }
如果你對這個應用很感興趣,你可以從這里獲取整個應用的源碼:gochatting.