Go 实现 Websocket服务以及代理
1. 协议说明
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。Websocket 主要用在B/S架构的应用程序中,在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
WebSocket 协议在2008年诞生,2011 年成为国际标准。现在最新版本浏览器都已经支持了。
WebSocket 是一种应用层协议
WebSocket 的典型特点:
基于 TCP 协议的应用层协议,实现相对简单
单个 TCP 连接上进行全双工通信
兼容 HTTP 协议,默认端口也是 80 和 443
ws://host:port/path/query
wss://host:port/path/query
握手阶段采用 HTTP 协议,能通过各种 HTTP 代理服务器
数据格式比较轻量,性能开销小,通信高效
可以发送文本和二进制数据
没有浏览器的同源限制
websocket 的典型场景:
即时通信
协同编辑/编辑
实时数据流的拉取与推送
2. WebSocket 推送和浏览器轮询
在 B/S 开发领域,若需要浏览器 B 即时得到服务器的状态更新,常使用两个方案:
浏览器端轮询
服务器端推送
浏览器轮询:浏览器端,当需要获取最新数据状态时,利用脚本程序循环向服务端发送请求。
服务器推送,服务器端,当状态改变时,将数据发送到浏览器端。
HTTP/2 版本也支持服务器端推送,但实现上以推送静态资源为主,不能基于业务逻辑推送特定的消息,因此当前的普及使用率websocket还是主流。
3. WebSocket 和 http
相同点
应用层协议
B/S 架构中使用
基于 TCP 协议
端口默认都是:80 和 443
不同点
4. WebSocket 握手过程
通过 HTTP 请求响应,中的头信息,完成 websocket 握手,如图:
在请求头中添加如下信息
# 升级为 websocket
Upgrade: websocket
Connection: Upgrade
# 一个 Base64 encode 的值,有于验证服务器端是否支持websocket
Sec-WebSocket-Key: x4JJHMbDL22zLk1GBhXDw==
# 用户协议,可以视为不同业务逻辑的频道
Sec-WebSocket-Protocol: chat
# 协议版本,13是当前通用版本,几乎不需要更改
Sec-WebSocket-Version: 13
基于以上请求头,服务器端,就知道需要将协议升级为 websocket 协议,并提供一些验证信息。
服务端的响应头
HTTP/1.1 101 Switching Protocols
# 协议升级
Upgrade: websocket
# 连接状态
Connection: Upgrade
# WebSocket服务端根据Sec-WebSocket-Key生成
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
# WebSocket协议用户协议
Sec-WebSocket-Protocol: chat
基于以上响应头,浏览器端就知道服务器端升级成功,并通过了验证。
至此,B/S 端可以基于该连接,完成 websocket 双向通信了。
websocket 只能发送 GET 请求
5. WebSocket 状态码和消息类型
5.1 状态码
WebSocket协议状态码解析_websocket状态码-CSDN博客
5.2 消息类型
TextMessage 和 BinaryMessage 分别表示发送文本消息和二级制消息
CloseMessage 关闭帧,接收方收到这个消息就关闭连接
PingMessage 和 PongMessage : 是保持心跳的帧
发送方 -> 接收方是 PingMessage
接收方 -> 发送方是 PongMessage
由服务器发 ping 给浏览器,浏览器返回 pong 消息
6. WebSocket 服务器实现
使用 github.com/gorilla/websocket
这个库函数
func WebSocketServer() {
addr := "localhost:8002"
http.HandleFunc("/wshandler", WebSocketUpgrade)
log.Println("Starting websocket server at " + addr)
go func() {
err := http.ListenAndServe(addr, nil)
if err != nil {
log.Fatal(err)
}
}()
log.Println("WebSocket 服务器正在运行。按Ctrl+C退出")
select {}
}
func WebSocketUpgrade(resp http.ResponseWriter, req *http.Request) {
// 初始化 Upgrader
upgrader := websocket.Upgrader{} // 使用默认的选项
// 第三个参数是响应头,默认会初始化
conn, err := upgrader.Upgrade(resp, req, nil)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
// 读取客户端的发送额消息,并返回
go ReadMessage(conn)
select {}
}
// 读取客户端发送的消息,并返回
func ReadMessage(conn *websocket.Conn) {
for {
// 消息类型:文本消息和二进制消息
messageType, msg, err := conn.ReadMessage()
if err != nil {
log.Println(err)
return
}
fmt.Println("receive msg:", string(msg))
err = conn.WriteMessage(messageType, msg)
if err != nil {
log.Println("write error:", err)
return
}
}
}
使用 Apifox 测试 websocket 是否能连接并且发送消息
消息发送成功,同时也接收到来服务端的消息
消息接收成功
7. WebSocket 代理实现
package websocket
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
)
var (
// 代理服务器地址
proxyServer = "127.0.0.1:8082"
// 真实websocket服务器地址
websocketServer = "http://127.0.0.1:8002"
)
func WebSocketProxy() {
url, err := url.Parse(websocketServer)
if err != nil {
log.Println(err)
}
proxy := httputil.NewSingleHostReverseProxy(url)
log.Println("WebSocket 代理启动, 按CTRL+C退出")
http.ListenAndServe(proxyServer, proxy)
}
8. WebSocket 服务端主动推送功能的实现
websocket 服务器每隔 3 秒会主动向服务器推送消息
"Heart Beat"
func WebSocketServer() {
addr := "localhost:8002"
http.HandleFunc("/wshandler", WebSocketUpgrade)
log.Println("Starting websocket server at " + addr)
go func() {
err := http.ListenAndServe(addr, nil)
if err != nil {
log.Fatal(err)
}
}()
log.Println("WebSocket 服务器正在运行。按Ctrl+C退出")
select {}
}
func WebSocketUpgrade(resp http.ResponseWriter, req *http.Request) {
// 初始化 Upgrader
upgrader := websocket.Upgrader{} // 使用默认的选项
// 第三个参数是响应头,默认会初始化
conn, err := upgrader.Upgrade(resp, req, nil)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
// 主动向服务端推送消息
go PushMessage(conn)
// 读取客户端的发送额消息,并返回
go ReadMessage(conn)
select {}
}
// websocket 服务器主动服务器推送消息
func PushMessage(conn *websocket.Conn) {
for {
err := conn.WriteMessage(websocket.TextMessage, []byte("heart beat"))
if err != nil {
log.Println(err)
return
}
time.Sleep(time.Second * 3)
}
}
// 读取客户端发送的消息,并返回
func ReadMessage(conn *websocket.Conn) {
for {
// 消息类型:文本消息和二进制消息
messageType, msg, err := conn.ReadMessage()
if err != nil {
log.Println(err)
return
}
fmt.Println("receive msg:", string(msg))
err = conn.WriteMessage(messageType, msg)
if err != nil {
log.Println("write error:", err)
return
}
}
}
每隔三秒可以看到服务推送过来的消息
评论区