使用 Go 和 WebSockets 构建实时聊天服务器

Nev5970 7年前
   <p><img src="https://simg.open-open.com/show/337551b5ca4ca4011df07a04d066850f.jpg"></p>    <p>现代网页应用程序正日趋丰富而复杂。像这样有趣又有活力的体验很受用户欢迎。用户无需向服务器发起调用,或刷新浏览器,就可以让页面实时更新。早期的开发者依赖 AJAX 来创建具备近乎实时体验的应用程序。而现在,他们运用 WebSockets 就能创建完全实时的应用程序了。</p>    <p>本教程中我们将使用 Go 编程语言以及 WebSockets 来创建一个实时的聊天应用程序。前端将会使用 HTML5 和 VueJS 来编写。该内容需要你对 Go 语言, JavaScript 以及 HTML5 有一个基础的了解,最好有一点点使用 VueJS 的经验。</p>    <h3>WebSocket 是什么?</h3>    <p>通常 Web 应用使用一个或多个请求对 HTTP 服务器提供对外服务。客户端软件通常是 Web 浏览器向服务器发送请求,服务器发回一个响应。响应通常是 HTML 内容,由浏览器来渲染为页面。样式表,JavaScript 代码和图像也可以在响应中发送回来以完成整个网页。每个请求和响应都属于特定的单独的连接的一部分,像 非死book 这样的大型网站为了渲染单个页面实际上可以产生数百个这样的连接。</p>    <p>AJAX 的工作方式跟这个完全相同。使用 JavaScript,开发人员可以向 HTTP 服务器请求一小段信息,然后根据响应更新部分页面。这可以在不刷新浏览器的情况下完成,但仍然存在一些限制。</p>    <p>每个 HTTP 请求/响应的连接在被响应之后都会关闭,因此获得任何新的信息必须新建另一个连接。如果没有新的请求发送给服务器,它就不知道客户端正在查找新的信息。能让 AJAX 应用程序看起来像实时的一种技术是定时循环发送 AJAX 请求。在设置了时间间隔之后,应用程序可以重新将请求发送到服务器,以查看是否有任何更新需要反馈给浏览器。这比较适合小型应用程序,但并不高效。这时候 WebSockets 就派上用场了。</p>    <p>WebSockets 是由 Internet 工程任务组(IETF)创建的建议标准的一部分。 RFC6455 中详细描述了 WebSockets 实现的完整技术规范。下面是该文档定义 WebSocket 的节选:</p>    <p>WebSocket 协议用于客户端代码和远程主机之间进行通信,其中客户端代码是在可控环境下的非授信代码</p>    <p>换句话说,WebSocket 是一个总是打开的连接,允许客户端和服务器自发地来回发送消息。服务器可在必要时将新信息推送到客户端,客户端也可以对服务器执行相同操作。</p>    <h3>JavaScript 中的 WebSockets</h3>    <p>大多数现代浏览器 都在其 JavaScript 实现中支持 WebSockets。要从浏览器中启动一个 WebSocket 连接,你可以使用简单的 WebSocket JavaScript 对象,如下:</p>    <pre>  <code class="language-go">var ws = new Websocket("ws://example.com/ws");</code></pre>    <p>您唯一需要的参数是一个 URL,WebSocket 连接可通过此 URL 连接服务器。该请求实际是一个 HTTP 请求,但为了安全连接我们使用“ws://”或“wss://”。这使服务器知道我们正在尝试创建一个新的 WebSocket 连接。之后服务器将“升级”该客户端和服务之间的连接到永久的双向连接。</p>    <p>一旦新的 WebSocket 对象被创建,并且连接成功创建之后,我们就可以使用“send()”方法发送文本到服务器,并在 WebSocket 的“onmessage”属性上定义一个处理函数来处理从服务器发送的消息。具体逻辑会在之后的聊天应用程序代码中解释。</p>    <h3>Go 中的 WebSockets</h3>    <p>WebSockets 并不包含在 Go 标准库中,但幸运的是有一些不错的第三方包让 WebSockets 的使用轻而易举。在这个例子中,我们将使用一个名为“gorilla/websocket”的包,它是流行的 Gorilla Toolkit 包集合的一部分,多用于在 Go 中创建 Web 应用程序。请运行以下命令进行安装:</p>    <pre>  <code class="language-go">$ go get github.com/gorilla/websocket</code></pre>    <h3>构建服务器</h3>    <p>这个应用程序的第一部分是服务器。这是一个处理请求的简单 HTTP 服务器。它将为我们提供 HTML5 和 JavaScript 代码,以及建立客户端的 WebSocket 连接。另外,服务器还将跟踪每个 WebSocket 连接并通过 WebSocket 连接将聊天信息从一个客户端发送到所有其他客户端。首先创建一个新的空目录,然后在该目录中创建一个“src”和“public”目录。在“src”目录中创建一个名为“main.go”的文件。</p>    <p>搭建服务器首先要进行一些设置。我们像所有 Go 应用程序一样启动应用程序,并定义包命名空间,在本例中为“main”。接下来我们导入一些有用的包。 “log”和“net/http”都是标准库的一部分,将用于日志记录并创建一个简单的 HTTP 服务器。最终包“github.com/gorilla/websocket”将帮助我们轻松创建和使用 WebSocket 连接。</p>    <pre>  <code class="language-go">package mainimport (          "log"          "net/http"            "github.com/gorilla/websocket")</code></pre>    <p>下面的两行代码是一些全局变量,在应用程序的其它地方会被用到。全局变量的实践较差,不过这次为了简单起见我们还是使用了它们。第一个变量是一个 map 映射,其键对应是一个指向 WebSocket 的指针,其值就是一个布尔值。我们实际上并不需要这个值,但使用的映射数据结构需要有一个映射值,这样做更容易添加和删除单项。</p>    <p>第二个变量是一个用于由客户端发送消息的队列,扮演通道的角色。在后面的代码中,我们会定义一个 goroutine 来从这个通道读取新消息,然后将它们发送给其它连接到服务器的客户端。</p>    <pre>  <code class="language-go">var clients = make(map[*websocket.Conn]bool) // connected clients  var broadcast = make(chan Message)           // broadcast channel</code></pre>    <p>接下来我们创建一个 upgrader 的实例。这只是一个对象,它具备一些方法,这些方法可以获取一个普通 HTTP 链接然后将其升级成一个 WebSocket,稍后会有相关代码介绍。</p>    <pre>  <code class="language-go">// Configure the upgrader  var upgrader = websocket.Upgrader{}</code></pre>    <p>最后我们将定义一个对象来管理消息,数据结构比较简单,带有一些字符串属性,一个 email 地址,一个用户名以及实际的消息内容。我们将利用 email 来展示 Gravatar 服务所提供的唯一身份标识。</p>    <p>如果从 socket 中读取数据有误,我们假设客户端已经因为某种原因断开。我们记录错误并从全局的 “clients” 映射表里删除该客户端,这样一来,我们不会继续尝试与其通信。</p>    <p>另外,HTTP 路由处理函数已经被作为 goroutines 运行。这使得 HTTP 服务器无需等待另一个连接完成,就能处理多个传入连接。</p>    <pre>  <code class="language-go">func handleConnections(w http.ResponseWriter, r *http.Request) {      ...          for {                  var msg Message                // Read in a new message as JSON and map it to a Message object                  err := ws.ReadJSON(&msg)                  if err != nil {                          log.Printf("error: %v", err)                          delete(clients, ws)                          break                  }                  // Send the newly received message to the broadcast channel                  broadcast <- msg        }  }</code></pre>    <p>服务器的最后一部分是 "handleMessages()"</p>    <p>“clients” 映射</p>    <pre>  <code class="language-go">func handleMessages() {          for {                  // Grab the next message from the broadcast channel                  msg := <-broadcast                  // Send it out to every client that is currently connected                  for client := range clients {                          err := client.WriteJSON(msg)                          if err != nil {                                  log.Printf("error: %v", err)                                  client.Close()                                  delete(clients, client)                          }                  }          }  }</code></pre>    <p> </p>    <p>来自:https://www.oschina.net/translate/build-a-realtime-chat-server-with-go-and-websockets</p>    <p> </p>