showliuzp 2025-10-23 16:02 采纳率: 84.3%
浏览 3
已结题

golang websocket读取和关闭帧的关系


    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil{
        return
    }
       
//样例1:

      //关闭回调函数没有被调用
        for{
        msg_type, msg, err := conn.ReadMessage()
        fmt.Printf("消息类型:%d,msg:%s,err:%+v\n",msg_type,msg,err)       
        conn.SetCloseHandler(func(code int, text string) error {
            fmt.Printf("读收到退出\n")
            return nil
        })
        time.Sleep(1*time.Second)
    }


//样例二:
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil{
        return
    }

       //关闭回调函数没有被调用
        for{        
        conn.SetCloseHandler(func(code int, text string) error {
            fmt.Printf("读收到退出\n")
            return nil
        })
        time.Sleep(1*time.Second)
    }

//样例三、

      //关闭回调函数被调用
        for{
        conn.SetCloseHandler(func(code int, text string) error {
            fmt.Printf("读收到退出\n")
            return nil
        })
        time.Sleep(1*time.Second)
      msg_type, msg, err := conn.ReadMessage()
        fmt.Printf("消息类型:%d,msg:%s,err:%+v\n",msg_type,msg,err)
       
    }

//websocket库:gorilla/websocket

我在测试websocket里的关闭帧触发逻辑,只有样例三的关闭回调函数可以被触发(跟样例12的区别是放入了ReadMessage读取,并且关闭回调函数设置在前),样例12都触发不了,有了解原理的么
  • 写回答

3条回答 默认 最新

  • 宝码香车 优质创作者: JavaScript技术领域 2025-10-23 16:09
    关注

    让【宝码香车】来帮您解答,本回答参考DeepSeek编写,并整理提供。

    如果答案让您满意,请采纳、点赞、关注,非常感谢!
    .
    每日一句正能量,让它带给您好心情:用心做好每一件小事,就是在为大事铺路。
    .

    Golang WebSocket 读取和关闭帧的关系分析

    问题分析

    从您提供的三个样例可以看出,关闭回调函数只有在与 ReadMessage() 配合使用时才会被触发。这涉及到 Gorilla WebSocket 库的内部实现机制。

    核心原理

    1. 关闭帧的处理机制

    在 Gorilla WebSocket 库中,关闭帧的处理是在读取消息的过程中进行的:

    • 当对端发送关闭帧时,该帧会被放入连接的消息队列中
    • 只有在调用 ReadMessage() 时,库才会检查并处理队列中的关闭帧
    • 如果没有调用 ReadMessage(),关闭帧不会被检测到,因此关闭回调也不会触发

    2. 三个样例的对比分析

    样例一和样例二的问题:

    for {
        // 没有调用 ReadMessage() 或者 ReadMessage() 在错误的位置
        conn.SetCloseHandler(func(code int, text string) error {
            fmt.Printf("读收到退出\n")
            return nil
        })
        time.Sleep(1*time.Second)
    }
    
    • 缺少 ReadMessage() 调用,无法检测到关闭帧
    • 关闭回调设置但永远不会被触发

    样例三的正确做法:

    for {
        conn.SetCloseHandler(func(code int, text string) error {
            fmt.Printf("读收到退出\n")
            return nil
        })
        time.Sleep(1*time.Second)
        msg_type, msg, err := conn.ReadMessage()
        fmt.Printf("消息类型:%d,msg:%s,err:%+v\n",msg_type,msg,err)
    }
    
    • 在循环中调用 ReadMessage(),能够检测到关闭帧
    • 关闭回调设置在读取之前,确保回调函数已注册

    正确的使用模式

    推荐写法:

    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        return
    }
    
    
    // 提前设置关闭处理器
    conn.SetCloseHandler(func(code int, text string) error {
        fmt.Printf("连接关闭,代码: %d, 原因: %s\n", code, text)
        return nil
    })
    
    
    // 持续读取消息
    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            // 这里会包含关闭相关的错误
            if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
                fmt.Println("正常关闭")
            }
            break
        }
        
        // 处理正常消息
        fmt.Printf("收到消息类型: %d, 内容: %s\n", messageType, string(message))
    }
    

    更完整的处理:

    conn.SetCloseHandler(func(code int, text string) error {
        fmt.Printf("收到关闭帧 - 代码: %d, 原因: %s\n", code, text)
        
        // 可以选择发送关闭确认
        message := websocket.FormatCloseMessage(code, "")
        conn.WriteMessage(websocket.CloseMessage, message)
        
        return nil
    })
    
    
    // 读取循环
    go func() {
        defer conn.Close()
        
        for {
            _, message, err := conn.ReadMessage()
            if err != nil {
                if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
                    fmt.Printf("意外关闭错误: %v\n", err)
                }
                break
            }
            
            // 处理业务逻辑
            fmt.Printf("收到消息: %s\n", string(message))
        }
    }()
    

    关键要点

    1. 关闭回调必须在读取操作之前设置
    2. 必须调用 ReadMessage() 才能触发关闭处理
    3. 关闭帧是作为特殊消息在读取时被处理的
    4. 建议在连接建立后立即设置关闭处理器

    这种设计确保了 WebSocket 协议的完整性,只有在主动读取消息时才会处理协议级别的控制帧(包括关闭帧)。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

问题事件

  • 系统已结题 10月31日
  • 已采纳回答 10月23日
  • 创建了问题 10月23日