在本系列文章中,将会使用在 Go 中一个用得比较多的 WebSocket 实现 gorilla/websocket。

背景知识 - HTTP 与 WebSocket 的关系

本文会涉及到一些原理讲解,其中比较关键的一个是 HTTP 与 WebSocket 的联系与区别,了解这个可以帮助我们更好地使用 WebSocket。

如果我们此前已经使用过 WebSocket,比如在 nginx 配置过 WebSocket,我们就会发现:

有个类似 upgrade 的关键字。这个关键字体现了 HTTP 与 WebSocket 的本质区别。在 nginx 里配置,意味着 WebSocket 本质上也是通过 HTTP 协议来工作的。

我们知道,HTTP 的请求会在请求结束之后断开 TCP 连接,但 WebSocket 不一样,它在建立连接之后会一直维持着连接状态, 这样客户端与服务端就可以一直维持通信状态了。

WebSocket 建立连接的过程

在 WebSocket 协议中,初始的握手阶段使用标准的 HTTP 请求和响应:

客户端先发送一个 HTTP 请求,请求升级到 WebSocket 协议。服务器在收到这个请求后,如果同意升级到 WebSocket,就会返回一个状态码为 101 的 HTTP 响应,指示升级成功,然后不会断开 TCP 连接。

这个过程涉及到的 HTTP 头部字段是 Upgrade 和 Connection,具体而言,HTTP 请求头部可能包含类似以下的字段:

请求:

GET /chat HTTP/1.1

Host: example.com

Upgrade: websocket

Connection: Upgrade

响应:

HTTP/1.1 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

也就是说,我们所看到的 Upgrade 实际上是把一个 HTTP 连接升级为了 WebSocket 连接,这个连接可以实现双向的通信。

这使得它非常适合实时通信的应用,例如聊天应用、在线游戏等。

gorilla/websocket 中的基本概念

WebSocket 连接 - Conn

在 gorilla/websocket 中使用 Conn 来表示一个 WebSocket 连接,它主要有如下作用:

发送消息给客户端:Write* 方法,如 WriteJSON 发送 JSON 类型消息,又或者 WriteMessage 可以发送普通的文本消息。接收客户端发送的消息:Read* 方法,如 ReadJSON 和 ReadMessage。其他功能:关闭连接、获取客户端 IP 地址等

消息

在 gorilla/websocket 中,消息被分为以下几种:

数据消息:

TextMessage 文本消息:文本消息被解析为 UTF-8 编码的文本。需要应用程序来确保文本消息是有效的 UTF-8 编码文本。BinaryMessage 二进制消息:二进制消息的解析留给应用程序。 控制消息:可以调用 Conn 中的 WriteControl、WriteMessage 或 NextWriter 方法,将控制消息发送给对方。

CloseMessage 关闭连接的消息PingMessage ping 消息PongMessage pong 消息

注意:应用程序需要先读取连接中的消息才能处理从对等方发送的 close、ping 和 pong 消息。如果应用程序对来自对等方的消息不感兴趣, 则应用程序应启动一个 goroutine 来读取和丢弃来自对等方的消息。

并发

虽然 Golang 中有 goroutine 可以支持我们做并发操作,但是在 gorilla/websocket 中, 一个 WebSocket 连接只支持一个并发 reader 和一个并发 writer。

我们的应用程序应该确保不超过一个 goroutine 同时调用写入方法(WriteMessage、WriteJSON)或者读取方法(ReadMessage、ReadJSON)。

而 Close 和 WriteControl 方法可以与其他所有方法同时调用。

安全性

我们知道,在一般的 web 应用中,经常需要处理跨域的问题,同样的,在 gorilla/websocket 中也需要做一定的配置。

我们可以在 Upgrader 中的 CheckOrigin 字段中指定函数的 Origin 检查策略,如果 CheckOrigin 函数返回 false,则 Upgrader 方法将拒绝建立 WebSocket 连接,如果允许所有来源的连接,我们可以直接返回 true 即可。

var upgrader = websocket.Upgrader{

ReadBufferSize: 1024,

WriteBufferSize: 1024,

CheckOrigin: func(r *http.Request) bool {

return true

},

}

缓冲

缓冲在 io 类操作中是一个很常见的术语,在 gorilla/websocket 中我们可以通过上面那段代码的 ReadBufferSize 和 WriteBufferSize 来指定连接的缓冲大小,以减少读取或写入消息时的系统调用次数。

默认大小为 4096,建议限制为最大预期消息的大小,大于最大消息最大大小的缓冲区不会带来任何好处。

Hello World

最后,让我们通过一个简单的 Hello World 程序来结束本文:

main.go

package main

import (

"github.com/gorilla/websocket"

"log"

"net/http"

)

var upgrader = websocket.Upgrader{

ReadBufferSize: 1024,

WriteBufferSize: 1024,

CheckOrigin: func(r *http.Request) bool {

return true

},

}

func handler(w http.ResponseWriter, r *http.Request) {

conn, err := upgrader.Upgrade(w, r, nil)

if err != nil {

log.Fatal(err)

}

conn.WriteMessage(websocket.TextMessage, []byte("Hello, World!"))

conn.Close()

}

func main() {

http.HandleFunc("/ws", handler)

http.ListenAndServe(":8181", nil)

}

执行 go run main.go 启动 WebSocket 服务端,然后,我们打开一个浏览器的控制台, 在里面执行下面的 JavaScript 代码:

let ws = new WebSocket('ws://127.0.0.1:8181/ws')

不出意外的话,我们可以在浏览器控制台的 Network -> WS 中看到由服务端发送的 Hello, World!。

精彩文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。