golang的websocket实战
1. 前言
有些场景下,比如交易 K 线,我们需要前端对后端进行轮询来不断获取或者更新资源状态。轮询的问题毫无以为是一种笨重的方式,因为每一次 http 请求除了本身的资源信息传输外还有三次握手以及四次挥手。替代轮询的一种方案是复用一个 http 连接,更准确的复用同一个 tcp 连接。这种方式可以是 http 长连接,也可以是 websocket。
1.1. http长连接
http 其实不存在长短连接, http协议的长连接和短连接,实质上是tcp协议的长连接和短连接。
http会话永远都是:请求响应结束,这里长连接指一次tcp连接可以传递多次的HTTP报文信息。
1.2 websocket
websocket协议是基于tcp的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
websocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。
WebSocket 更像一通已经接通的电话:连接建立后,客户端和服务端都可以随时发送消息。业务消息不是 HTTP 那种“一问一答”的固定模型,一方发送业务数据后,另一方可以回复,也可以不回复,这取决于应用协议本身。
需要区分两类消息:
- 业务消息:是否响应由业务决定。例如行情推送、通知广播可以只发不回。
- 协议层心跳:WebSocket 协议有
Ping/Pong控制帧,用来检测底层连接是否还活着。这个层面的心跳一般由浏览器或服务端框架处理。
如果业务上需要知道“回复的是哪一条请求”,要在应用层消息体里加唯一 ID,例如 request_id、correlation_id。多个业务 ping 和多个业务 pong 的对应关系不能只靠发送顺序猜,应该靠这个 ID 匹配。
WebSocket 发送也是异步的。调用 send 或 WriteMessage 后,数据会进入运行时、操作系统和 TCP 的发送路径,代码不会等待对端业务处理完成。持续发送虽然可行,但不是无限制的:底层 TCP 有流量控制,如果接收方处理不过来,发送方最终会被缓冲区和写超时限制住。
1.3 websocket 和 http 长连接的区别
首先 websocket 和 http 是完全不同的两种协议,虽然底层都是 tcp/ip。http 长连接也是属于 http 协议。
http 协议和 websocket 的最大区别就是 http 是基于 request/response 模式,而 websocket 的 client 和 server 端却可以随意发起 data push。
1.4 sse(server-sent events)
sse是 websockct 的一种轻量代替方案,使用 http 协议。sse 规范是 html5 规范的一个组成部分。
严格地说,http无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(Content-Type: text/event-stream)。
总体来说,websocket 更强大和灵活。因为它是全双工通道,可以双向通信;sse 是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载。如果浏览器向服务器发送信息,就变成了另一次 http 请求。
2. golang websocket
在golang语言中,目前有两种比较常用的实现方式:一个是golang自带的库,另一个是gorilla,后者功能更加强大。
2.1 server端
下面server端是一个http 服务器,监听8080端口。当接收到连接请求后,将连接使用的http协议升级为websocket协议。后续通信过程中,使用websocket进行通信。
对每个连接,server端等待读取数据,读到数据后,打印数据,然后,将数据又发送给client
1 | package main |
2.2 client端
client启动后,首先连接server。连接建立后,主routine每一秒钟向server发送消息(当前时间)。另一个routine从server接收数据,并打印。
当client退出时,会向server发送关闭消息。接着,等待退出。
1 | package main |

3. 总结
服务器:
- var upgrader = websocket.Upgrader{}
- c, err := upgrader.Upgrade(w, r, nil)
- for循环里 c.ReadMessage() 和 c.WriteMessage(mt, message)
客户端:
- c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
- for循环里 c.ReadMessage() 和 c.WriteMessage(mt, message)