嗨,开发人员!在本教程中,我们将研究如何在我们自己的基于 Go 的程序中使用 WebSockets 来做一些很酷的实时事情。
在本教程结束时,我们应该已经涵盖了以下内容:
出于本教程的目的,我们将使用该gorilla/websocket软件包,因为我曾在一些生产应用程序中亲自使用它并取得了巨大的成功。
因此,我在许多不同的教程中多次介绍了这一点,但始终值得一提的是我们为什么使用 WebSocket 以及它们与传统HTTP请求有何不同。
WebSockets 是升级后的 HTTP 连接,在连接被客户端或服务器终止之前一直存在。正是通过这个 WebSocket 连接,我们可以执行双工通信,这是一种非常奇特的方式,可以说我们可以使用这个单一连接从我们的客户端与服务器进行通信。
WebSockets 的真正美妙之处在于它们总共使用了 1 个 TCP 连接,并且所有通信都是通过这个单一的长寿命 TCP 连接完成的。这大大减少了使用 WebSockets 构建实时应用程序所需的网络开销,因为不需要对 HTTP 端点进行持续轮询。
让我们从一个非常简单的 Go 程序开始,一旦我们可以运行它,我们就可以在它之上构建并开始构建一些简单的 WebSocket 端点。
我们需要首先go modules通过调用来初始化我们的项目以使用:
$ go mod init github.com/elliotforbes/go-websocket-tutorial
这个命令应该go.mod在我们的当前目录中创建一个文件。
完成后,我们可以继续定义我们的main.go文件,我们将在其中进行大部分编码:
main.go
package mainimport "fmt"func main() { fmt.Println("Hello World")}
让我们通过打开终端、导航到项目目录然后调用go run main.go. 我们应该看到它Hello World在我们的终端中成功输出。
我们将从构建一个简单的 HTTP 服务器开始,Hello World只要我们在 port 上点击它就会返回8080。我们还将定义一个简单的 HTTP 端点,它将作为我们将要创建的 WebSocket 端点的基础:
package mainimport ( "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 服务器,http://localhost:8080随后我们应该能够在我们的浏览器中点击/和/ws路由。
为了创建 WebSocket 端点,我们实际上需要将传入连接从标准 HTTP 端点升级为持久的 WebSocket 连接。为了做到这一点,我们将使用非常酷的gorilla/websocket软件包中的一些功能!
我们要做的第一件事是定义一个websocker.Upgrader结构。这将保存诸如 WebSocket 连接的读取和写入缓冲区大小之类的信息:
// We'll need to define an Upgrader// this will require a Read and Write buffer sizevar 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 请求的指针的函数来升级传入的 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()现在将调用它,它将接收一个指向我们从调用中收到的 WebSocket 连接的指针upgrader.Upgrade:
// define a reader which will listen for// new messages being sent to our WebSocket// endpointfunc 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.goHello 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>
如果我们index.html在浏览器中打开此页面,我们应该在控制台中看到它已经能够成功连接!
我们还应该在我们的服务器日志中看到客户端已成功连接以及一条Hi From The Client!消息!
我们现在已经实现了全双工通信!
我在其他许多教程中都谈到了 Docker 的好处,但本质上,它允许我们定义应用程序成功运行所需的确切环境。这意味着您应该能够在 10 年内运行本教程,并且它应该仍然可以完美运行。
FROM golang:1.11.1-alpine3.8RUN mkdir /appADD . /app/WORKDIR /appRUN go mod downloadRUN go build -o main ./...CMD ["/app/main"]
现在我们已经Dockerfile为我们的应用程序定义了这个,让我们image 使用docker build命令创建,然后让我们尝试在 Docker 中运行这个 Go WebSocket 应用程序container。
$ docker build -t go-websocket-tutorial .$ docker run -it -p 8080:8080 go-websocket-tutorial
如果一切顺利,我们应该能够看到我们的应用程序在映射到本地机器端口的 docker 容器中启动并运行8080。如果我们http://localhost:8080在浏览器中打开 ,我们应该会看到我们的应用程序返回home page。
在本教程中,我们设法介绍了 WebSockets 的一些基础知识,以及如何在 Go 中构建一个简单的基于 WebSocket 的应用程序!
因此,希望您喜欢本教程并发现它很有用!我希望这突出了在您自己的 Go 应用程序中使用 WebSockets 的一些主要好处!