从HTTP到WebSocket:Golang中WebSocket连接的实现与应用
一、WebSocket协议简介
WebSocket是一种计算机通信协议,提供全双工通信渠道,通过单个TCP连接实现。WebSocket最大的特点是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话。与HTTP协议不同的是:- WebSocket只需要一次握手,就可以建立持久连接
- 建立连接后,客户端和服务器可以随时互相发送数据
- 数据格式轻量,开销小
二、WebSocket连接建立过程
WebSocket连接是通过HTTP升级机制建立的,具体步骤如下:- 客户端发起连接请求:客户端发送一个普通的HTTP请求,但在请求头中包含特殊字段
- 服务器进行协议升级:服务器检查请求头,确认是WebSocket握手请求
- 连接建立成功:服务器返回101状态码(Switching Protocols),双方开始使用WebSocket协议通信
三、服务器端实现(基于Golang)
以下是一个WebSocket服务器的实现示例(基于提供的代码):
package gnet
import (
"github.com/gorilla/websocket"
"net"
"net/http"
"sync/atomic"
)
// WSAcceptor 结构体用于接受和管理WebSocket连接
type WSAcceptor struct {
host string // 主机地址
port string // 端口号
listener net.Listener // 网络监听器
factory HandlerFactory // 处理器工厂,用于创建连接处理器
codec *Codec // 编解码器,用于处理消息的编码和解码
stop chan bool // 停止信号通道
ccu int32 // 当前连接用户数
}
// Start 方法启动WebSocket接收器
func (acceptor *WSAcceptor) Start(port string, factory HandlerFactory, isGzip bool, maxIncomingPacket uint32) error {
acceptor.codec = NewCodec(isGzip, maxIncomingPacket)
acceptor.factory = factory
acceptor.host = ""
acceptor.port = port
address := net.JoinHostPort("", port)
listener, err := net.Listen("tcp", address)
if err != nil {
return err
}
acceptor.listener = listener
// 启动接受连接的循环
go acceptor.startAcceptLoop()
return nil
}
// startAcceptLoop 方法开始接受连接的循环
func (acceptor *WSAcceptor) startAcceptLoop() {
http.HandleFunc("/", acceptor.wsHandler)
if err := http.Serve(acceptor.listener, nil); err != nil {
panic(err)
}
}
// wsHandler 处理WebSocket连接请求
func (acceptor *WSAcceptor) wsHandler(w http.ResponseWriter, r *http.Request) {
select {
case <-acceptor.stop:
return
default:
// 创建WebSocket升级器
upgrader := websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // 允许所有来源的WebSocket连接
},
}
// 将HTTP连接升级为WebSocket连接
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
// 处理WebSocket连接
wsConn := NewWSConn(conn, acceptor.codec, acceptor.stop)
wsConn.delegate = acceptor.factory(wsConn)
go func() {
defer atomic.AddInt32(&acceptor.ccu, -1)
atomic.AddInt32(&acceptor.ccu, 1)
wsConn.Start()
}()
}
}
// Stop 方法停止WebSocket接收器
func (acceptor *WSAcceptor) Stop() {
close(acceptor.stop)
_ = acceptor.listener.Close()
}
// GetCcu 方法返回当前连接用户数
func (acceptor *WSAcceptor) GetCcu() int32 {
return atomic.LoadInt32(&acceptor.ccu)
}
代码解析
- WSAcceptor结构体:
- 负责接受和管理WebSocket连接
- 维护监听器、处理器工厂等组件
- Start方法:
- 初始化监听器和其他组件
- 启动接受连接的循环
- wsHandler方法:
- 处理HTTP请求并升级为WebSocket连接
- 使用upgrader.Upgrade()函数完成协议升级
- 创建新的连接对象并启动处理
四、客户端实现(Golang版本)
以下是一个与上述服务器配套的Golang客户端示例:
package main
import (
"fmt"
"github.com/gorilla/websocket"
"log"
"net/url"
"os"
"os/signal"
"time"
)
func main() {
// 构建WebSocket URL
u := url.URL{Scheme: "ws", Host: "localhost:8080", Path: "/"}
// 记录尝试连接的日志
log.Printf("连接到 %s", u.String())
// 建立WebSocket连接
conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("连接失败:", err)
}
defer conn.Close()
// 处理中断信号
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
// 创建用于接收消息的通道
done := make(chan struct{})
// 启动读取消息的goroutine
go func() {
defer close(done)
for {
// 读取消息
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("读取错误:", err)
return
}
log.Printf("收到消息: %s", message)
}
}()
// 创建定时发送消息的ticker
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
// 消息计数器
msgCount := 0
// 主循环
for {
select {
case <-done:
return
case <-ticker.C:
msgCount++
message := fmt.Sprintf("Hello %d", msgCount)
// 发送消息
err := conn.WriteMessage(websocket.TextMessage, []byte(message))
if err != nil {
log.Println("写入错误:", err)
return
}
log.Printf("发送消息: %s", message)
case <-interrupt:
log.Println("收到中断信号,关闭连接...")
// 关闭WebSocket连接
err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
log.Println("写入关闭消息错误:", err)
return
}
// 等待服务器确认关闭
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}
客户端代码解析
- 连接建立:
- 使用websocket.DefaultDialer.Dial函数建立WebSocket连接
- 连接成功后返回连接对象
- 消息处理:
- 使用goroutine异步读取消息
- 使用定时器定期发送消息
- 连接关闭:
- 监听中断信号
- 发送正常关闭的消息
- 优雅地关闭连接
五、连接建立详解
1. HTTP升级为WebSocket的请求头
客户端发送的HTTP请求头包含:
ET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
2. 服务器响应头
服务器返回的响应头包含:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
3. 连接升级过程
在服务器代码中,关键的升级过程由以下代码完成:
upgrader := websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
conn, err := upgrader.Upgrade(w, r, nil)
这个过程中:
- 服务器检查客户端的HTTP请求是否包含正确的WebSocket握手头部
- 服务器生成响应头并发送101状态码
- TCP连接从HTTP协议切换到WebSocket协议
六、实践建议与注意事项
1. 安全性考虑
在示例代码中,CheckOrigin函数设置为允许所有来源的连接,这在生产环境中不安全。应该根据实际需求限制允许连接的来源:
upgrader := websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
// 检查Origin是否在允许的列表中
origin := r.Header.Get("Origin")
return isOriginAllowed(origin)
},
}
2. 心跳机制
WebSocket连接可能因为网络问题或防火墙设置而断开,因此应该实现心跳机制:
// 服务器端
func (conn *WSConn) startPingPong() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := conn.conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(time.Second)); err != nil {
return
}
case <-conn.stop:
return
}
}
}
// 客户端
conn.SetPingHandler(func(string) error {
conn.WriteControl(websocket.PongMessage, []byte{}, time.Now().Add(time.Second))
return nil
})
3. 错误处理与重连
客户端应当处理连接断开的情况,并尝试重新连接:
func connectWithRetry(urlStr string, maxRetries int) (*websocket.Conn, error) {
var conn *websocket.Conn
var err error
for i := 0; i < maxRetries; i++ {
conn, _, err = websocket.DefaultDialer.Dial(urlStr, nil)
if err == nil {
return conn, nil
}
log.Printf("连接失败,%d秒后重试: %v", i+1, err)
time.Sleep(time.Duration(i+1) * time.Second)
}
return nil, fmt.Errorf("达到最大重试次数,连接失败: %v", err)
}
七、总结
WebSocket连接建立是通过HTTP协议升级机制完成的,这一过程包括:
- 客户端发送带有特定请求头的HTTP请求
- 服务器验证请求头并返回101状态码
- TCP连接从HTTP协议切换到WebSocket协议
一旦连接建立,客户端和服务器就可以通过这个全双工连接自由地互相发送消息,而不必等待对方的响应。这使得WebSocket特别适用于需要实时通信的应用,如聊天、游戏、实时数据更新等场景。通过本文提供的Golang服务器和客户端示例,你可以快速搭建自己的WebSocket应用,实现实时双向通信功能。希望这篇博客能帮助你在将来快速回忆WebSocket的工作原理和实现方式!

浙公网安备 33010602011771号