一、WebSocket协议简介
WebSocket是一种计算机通信协议,提供全双工通信渠道,通过单个TCP连接实现。WebSocket最大的特点是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话。与HTTP协议不同的是:- WebSocket只需要一次握手,就可以建立持久连接
- 建立连接后,客户端和服务器可以随时互相发送数据
- 数据格式轻量,开销小
二、WebSocket连接建立过程
WebSocket连接是通过HTTP升级机制建立的,具体步骤如下:- 客户端发起连接请求:客户端发送一个普通的HTTP请求,但在请求头中包含特殊字段
- 服务器进行协议升级:服务器检查请求头,确认是WebSocket握手请求
- 连接建立成功:服务器返回101状态码(Switching Protocols),双方开始使用WebSocket协议通信
三、服务器端实现(基于Golang)
以下是一个WebSocket服务器的实现示例(基于提供的代码):
package gnetimport ("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 = factoryacceptor.host = ""acceptor.port = portaddress := 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:returndefault:// 创建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 mainimport ("fmt""github.com/gorilla/websocket""log""net/url""os""os/signal""time" )func main() {// 构建WebSocket URLu := 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{})// 启动读取消息的goroutinego func() {defer close(done)for {// 读取消息_, message, err := conn.ReadMessage()if err != nil {log.Println("读取错误:", err)return}log.Printf("收到消息: %s", message)}}()// 创建定时发送消息的tickerticker := time.NewTicker(time.Second)defer ticker.Stop()// 消息计数器msgCount := 0// 主循环for {select {case <-done:returncase <-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.Connvar err errorfor 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的工作原理和实现方式!