Go WebSocket
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 请求-响应模式不同,WebSocket 允许服务器和客户端之间进行实时、双向的数据传输。这使得 WebSocket 成为构建实时应用程序(如聊天应用、在线游戏和实时通知系统)的理想选择。
WebSocket 基础
WebSocket 协议通过 HTTP 握手建立连接,之后升级为 WebSocket 协议。一旦连接建立,客户端和服务器可以随时发送数据,而不需要等待对方的请求。
WebSocket 握手
WebSocket 连接始于一个 HTTP 请求,其中包含一个特殊的 Upgrade
头,指示客户端希望将连接升级为 WebSocket。服务器如果支持 WebSocket,则会响应一个 101 Switching Protocols
状态码,表示连接已成功升级。
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
服务器响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
WebSocket 数据帧
WebSocket 数据以帧的形式传输。帧可以是文本、二进制数据或控制帧(如关闭连接或 ping/pong 帧)。WebSocket 协议定义了如何将这些帧编码和解码。
在 Go 中使用 WebSocket
Go 标准库中没有直接支持 WebSocket 的包,但我们可以使用 gorilla/websocket
包来实现 WebSocket 功能。
安装 gorilla/websocket
首先,使用以下命令安装 gorilla/websocket
包:
go get github.com/gorilla/websocket
创建 WebSocket 服务器
以下是一个简单的 WebSocket 服务器示例,它接受客户端连接并回显收到的消息。
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func echoHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade error:", err)
return
}
defer conn.Close()
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Println("Read error:", err)
break
}
log.Printf("Received: %s", message)
if err := conn.WriteMessage(messageType, message); err != nil {
log.Println("Write error:", err)
break
}
}
}
func main() {
http.HandleFunc("/echo", echoHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
创建 WebSocket 客户端
以下是一个简单的 WebSocket 客户端示例,它连接到服务器并发送消息。
package main
import (
"log"
"os"
"os/signal"
"time"
"github.com/gorilla/websocket"
)
func main() {
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
url := "ws://localhost:8080/echo"
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Fatal("Dial error:", err)
}
defer conn.Close()
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("Read error:", err)
return
}
log.Printf("Received: %s", message)
}
}()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-done:
return
case t := <-ticker.C:
err := conn.WriteMessage(websocket.TextMessage, []byte(t.String()))
if err != nil {
log.Println("Write error:", err)
return
}
case <-interrupt:
log.Println("Interrupt received, closing connection...")
err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
log.Println("Write close error:", err)
return
}
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}
运行示例
- 启动服务器:
go run server.go
- 启动客户端:
go run client.go
客户端将每秒向服务器发送一条消息,并打印服务器返回的消息。
实际应用场景
WebSocket 在许多实时应用中都有广泛的应用,例如:
- 聊天应用:用户可以在聊天室中实时发送和接收消息。
- 在线游戏:玩家可以实时交互,游戏状态可以实时更新。
- 实时通知:系统可以向用户推送实时通知,如新消息或系统更新。
总结
WebSocket 提供了一种高效的方式来实现客户端和服务器之间的实时通信。通过 gorilla/websocket
包,我们可以轻松地在 Go 中实现 WebSocket 功能。本文介绍了 WebSocket 的基础知识,并提供了一个简单的服务器和客户端示例。
附加资源
练习
- 修改服务器代码,使其能够处理多个客户端连接。
- 扩展客户端代码,使其能够从标准输入读取消息并发送到服务器。
- 实现一个简单的聊天应用,允许多个用户在同一个聊天室中交流。