cmsmdn 2012-08-06
这个应用演示如何使用 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."); }