使用 Go 语言和 HTML5 WebSocket 构建一个 Web 聊天室

红薯 发布于 2012/08/04 09:57
阅读 23K+
收藏 87

这个应用演示如何使用 Google Go 语言和 HTML5 的 WebSocket 来实现一个简单的基于 Web 的聊天程序。

下图是聊天应用的截图:

Chat demo

你可输入 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.

英文原文OSCHINA原创翻译

加载中
0
洪瑞琦
洪瑞琦
操作字典的时候是不是应该加锁?
洪瑞琦
洪瑞琦
单线程只是默认值,写代码的时候必须为GOMAXPROC>1,成为多线程考虑。
乌龟壳
乌龟壳
据说是单线程的,天然不用加锁。
洪瑞琦
洪瑞琦
runningActiveRoom.OnlineUsers
纠结名字_我艹你妹
纠结名字_我艹你妹
哪个字典啊 可以解释下不
0
咯咯鸡
咯咯鸡

引用来自“紫外线”的答案

够烂
做个更好的晒一下吧
0
五杀联盟
五杀联盟
红薯还会golang?
0
紫外线
紫外线

引用来自“冷月孤寒”的答案

引用来自“紫外线”的答案

够烂
做个更好的晒一下吧
golang的中文名罢了
0
伟征
伟征

Go 准备再自学一门语言,Go怎么样。

或者其他的 推荐一下吧。

现在用C++项目开发。

0
Kvein
Kvein
go 语言 前面听过 好像说可以和c语言比拟
0
Kvein
Kvein
==下下来看了  部署环境是啥   后缀名是 .go  不知道咋部署的?  
Kvein
Kvein
怎么编译成可执行文件? 不需要部署环境、如exe的程序?
洪瑞琦
洪瑞琦
go 是编译语言,编译成可执行文件运行即可
0
ayang
ayang
创意很好啊
返回顶部
顶部