跳转到主要内容

嗨,开发人员! 在本教程中,我们将研究如何在我们自己的基于 Go 的程序中使用 WebSockets 来做一些很酷的实时事情。

在本教程结束时,我们应该已经涵盖了以下内容:

  • 什么是 WebSocket
  • 我们如何在 Go 中构建简单的 WebSocket 应用程序

出于本教程的目的,我们将使用 gorilla/websocket 包,因为我个人在一些生产应用程序中使用它并取得了巨大成功。

视频教程

https://youtu.be/dniVs0xKYKk

WebSockets - 它们是什么?


因此,我在许多不同的教程中多次介绍了这一点,但始终值得一提的是我们为什么使用 WebSocket 以及它们与传统 HTTP 请求的不同之处。

WebSockets 是升级后的 HTTP 连接,在连接被客户端或服务器终止之前一直存在。正是通过这个 WebSocket 连接,我们可以执行双工通信,这是一种非常奇特的方式,可以说我们可以使用这个单一连接从我们的客户端与服务器进行通信。

WebSockets 的真正美妙之处在于它们总共使用了 1 个 TCP 连接,并且所有通信都是通过这个单一的长寿命 TCP 连接完成的。这大大减少了使用 WebSockets 构建实时应用程序所需的网络开销,因为不需要对 HTTP 端点进行持续轮询。

一个简单的例子


让我们从一个非常简单的 Go 程序开始,一旦我们可以运行它,我们就可以在它之上构建并开始构建一些简单的 WebSocket 端点。

我们需要首先通过调用来初始化我们的项目以使用 go 模块:

$ go mod init github.com/elliotforbes/go-websocket-tutorial


这个命令应该在我们当前目录中创建一个 go.mod 文件。

完成此操作后,我们可以继续定义 main.go 文件,我们将在其中进行大部分编码:

main.go

package main

import "fmt"

func main() {
    fmt.Println("Hello World")
}

让我们通过打开终端、导航到项目目录然后调用 go run main.go 来检查是否可以运行它。我们应该看到它在我们的终端中成功输出了 Hello World。

一个简单的 HTTP 端点


我们将从构建一个简单的 HTTP 服务器开始,只要我们在端口 8080 上点击它就会返回 Hello World。我们还将定义一个简单的 HTTP 端点,它将作为我们将创建的 WebSocket 端点的基础:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Home Page")
}

func wsEndpoint(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World")
}

func setupRoutes() {
    http.HandleFunc("/", homePage)
    http.HandleFunc("/ws", wsEndpoint)
}

func main() {
    fmt.Println("Hello World")
    setupRoutes()
    log.Fatal(http.ListenAndServe(":8080", nil))
}

太棒了,当我们尝试使用 go run main.go 运行它时,我们应该看到它成功地在 http://localhost:8080 上启动了我们新定义的 HTTP 服务器,随后我们应该能够同时访问 / 和 /ws 路由在我们的浏览器中。

升级 HTTP 连接


为了创建 WebSocket 端点,我们实际上需要将传入连接从标准 HTTP 端点升级为持久的 WebSocket 连接。为了做到这一点,我们将使用非常酷的 gorilla/websocket 包中的一些功能!

定义我们的升级程序


我们要做的第一件事是定义一个 websocker.Upgrader 结构。这将保存诸如 WebSocket 连接的读取和写入缓冲区大小之类的信息:

// We'll need to define an Upgrader
// this will require a Read and Write buffer size
var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
}


检查传入来源


我们要添加到现有 wsEndpoint 函数的下一件事是调用 upgrader.CheckOrigin。这将确定是否允许来自不同域的传入请求连接,如果不允许连接,它们将遇到 CORS 错误。

func wsEndpoint(w http.ResponseWriter, r *http.Request) {
    // remove the previous fmt statement
    // fmt.Fprintf(w, "Hello World")
    upgrader.CheckOrigin = func(r *http.Request) bool { return true }

}

目前,我们保持它非常简单,无论哪个主机尝试连接到我们的端点,都简单地返回 true。

升级我们的连接


我们现在可以开始尝试使用 upgrader.Upgrade() 函数升级传入的 HTTP 连接,该函数将接收 Response Writer 和指向 HTTP 请求的指针,并返回一个指向 WebSocket 连接的指针,如果失败则返回错误升级。

func wsEndpoint(w http.ResponseWriter, r *http.Request) {
    upgrader.CheckOrigin = func(r *http.Request) bool { return true }

    // upgrade this connection to a WebSocket
    // connection
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
    }

}

持续聆听该连接


接下来,我们将要实现一个函数,该函数将持续侦听通过该 WebSocket 连接发送的任何传入消息。我们现在将调用此 reader(),它将接收一个指向我们从调用 upgrader.Upgrade 时收到的 WebSocket 连接的指针:

// define a reader which will listen for
// new messages being sent to our WebSocket
// endpoint
func reader(conn *websocket.Conn) {
    for {
    // read in a message
        messageType, p, err := conn.ReadMessage()
        if err != nil {
            log.Println(err)
            return
        }
    // print out that message for clarity
        fmt.Println(string(p))

        if err := conn.WriteMessage(messageType, p); err != nil {
            log.Println(err)
            return
        }

    }
}

有了这个定义,我们就可以将它添加到我们的 wsEndpoint 函数中,如下所示:

func wsEndpoint(w http.ResponseWriter, r *http.Request) {
    upgrader.CheckOrigin = func(r *http.Request) bool { return true }

    // upgrade this connection to a WebSocket
    // connection
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
    }
    // helpful log statement to show connections
    log.Println("Client Connected")

    reader(ws)
}

有了所有这些,我们现在应该能够像这样运行我们的新 WebSocket 服务器:

$ go run main.go
Hello World


太棒了,一切似乎都奏效了!

回信给我们的客户


我之前提到过,WebSockets 允许双工通信,即跨同一个 TCP 连接的来回通信。为了从我们的 Go 应用程序向任何连接的 WebSocket 客户端发送消息,我们可以像这样使用 ws.WriteMessage() 函数:

func wsEndpoint(w http.ResponseWriter, r *http.Request) {
    upgrader.CheckOrigin = func(r *http.Request) bool { return true }

    // upgrade this connection to a WebSocket
    // connection
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
    }

    log.Println("Client Connected")
    err = ws.WriteMessage(1, []byte("Hi Client!"))
    if err != nil {
        log.Println(err)
    }
    // listen indefinitely for new messages coming
    // through on our WebSocket connection
    reader(ws)
}

这个添加意味着任何连接到我们的 WebSocket 服务器的客户端都会受到一个很好的 Hi Client 的欢迎!信息!

测试与客户一起工作


最后一步是通过创建客户端并尝试连接到我们的新 WebSocket 端点来测试是否一切正常。为此,我们将创建一个非常简单的原生 JavaScript 应用程序,它将连接到 ws://localhost:8080/ws 并尝试通过这个新的 WebSocket 连接发送消息:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Go WebSocket Tutorial</title>
  </head>
  <body>
    <h2>Hello World</h2>

    <script>
        let socket = new WebSocket("ws://127.0.0.1:8080/ws");
        console.log("Attempting Connection...");

        socket.onopen = () => {
            console.log("Successfully Connected");
            socket.send("Hi From the Client!")
        };
        
        socket.onclose = event => {
            console.log("Socket Closed Connection: ", event);
            socket.send("Client Closed!")
        };

        socket.onerror = error => {
            console.log("Socket Error: ", error);
        };

    </script>
  </body>
</html>

您可以通过多种方式在本地提供此页面。 我倾向于用于验证的最快方法是使用名为 live-server 的 npm 库:

$ npm install -g live-server
$ live-server


这将在我首选的浏览器中自动打开一个窗口,并提供当前目录中的任何文件,这非常好。

现在,随着我们的前端启动并运行,我们可以打开浏览器的控制台工具,在控制台中我们应该看到它已经能够成功连接到我们的 WebSocket 服务器!

我们还应该在我们的服务器日志中看到客户端已成功连接以及来自客户端的 Hi! 信息!

我们现在已经实现了全双工通信!

Docker 化我们的应用程序


我在其他许多教程中都谈到了 Docker 的好处,但本质上,它允许我们定义应用程序成功运行所需的确切环境。这意味着您应该能够在 10 年内运行本教程,并且它应该仍然可以完美运行。

FROM golang:1.11.1-alpine3.8
RUN mkdir /app
ADD . /app/
WORKDIR /app
RUN go mod download
RUN go build -o main ./...
CMD ["/app/main"]


现在我们已经为我们的应用程序定义了这个 Dockerfile,让我们使用 docker build 命令创建图像,然后尝试在 Docker 容器中运行这个 Go WebSocket 应用程序。

$ docker build -t go-websocket-tutorial .
$ docker run -it -p 8080:8080 go-websocket-tutorial


如果一切顺利,我们应该能够看到我们的应用程序在映射到本地机器端口 8080 的 docker 容器中启动并运行。如果我们在浏览器中打开 http://localhost:8080,我们应该会看到我们的应用程序返回主页。

结论


在本教程中,我们设法介绍了 WebSockets 的一些基础知识,以及如何在 Go 中构建一个简单的基于 WebSocket 的应用程序!

因此,希望您喜欢本教程并发现它很有用!我希望这突出了在您自己的 Go 应用程序中使用 WebSockets 的一些主要好处!

源代码 - 本教程的完整源代码可以在这里找到:TutorialEdge/Go

延伸阅读


如果你喜欢这篇文章,你可能会喜欢我的教程系列,它利用 WebSockets 使用 React 和 Golang 构建实时聊天应用程序。它涵盖了如何汇集 WebSocket 连接并执行诸如向所有连接的客户端广播更新之类的事情!

  • 在 React and Go 中创建聊天应用程序
  • Go 多阶段 Dockerfile
文章链接