showliuzp 2025-10-23 15:17 采纳率: 84.3%
浏览 5
已结题

golang gorillawebsocket关闭回调函数不起作用

import (
    "github.com/gorilla/websocket"
    "fmt"
    "time"
    "sync"
)
func (ws *WsChat) WsChatCreateConn(operator_id int64,w http.ResponseWriter, r *http.Request) (err error){
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil{
        return
    }

    //读取一个连接的数据
    //go ws.WsChatMsgRead(operator_id,conn)

    //写入一个连接的数据
    go ws.WsChatMsgWrite(operator_id,conn)

  for{
time.Sleep(1*time.Second)
}
}
func (ws *WsChat) WsChatMsgWrite(operator_id int64,conn *websocket.Conn,exit_chan chan int64){

    //收到关闭帧退出,
    
        conn.SetCloseHandler(func(code int, text string) error {
        fmt.Printf("写入退出帧,code:%d,%s\n",code,text)
        return nil
        })
        fmt.Printf("循环\n")
}

我用postman建立socket连接后,关闭socket应该触发SetCloseHandler函数,即使放在for循环里也不行,是使用上有问题么
  • 写回答

3条回答 默认 最新

  • Juan_2012 2025-10-23 18:59
    关注

    回答参考qwen
    你提出的问题非常典型:**gorilla/websocketSetCloseHandler 回调函数没有被触发**,即使你在 Postman 中关闭了 WebSocket 连接。

    我们来逐步分析原因,并给出正确的使用方式和修复方案。


    🔍 问题核心

    SetCloseHandler 必须在 读取消息循环开始前设置,并且需要有持续的 ReadMessage() 调用才能生效!

    你的代码中:

    • 没有启动读取消息的 goroutine(虽然注释了 WsChatMsgRead
    • 写入函数里才设置 SetCloseHandler
    • 主协程只是死循环 Sleep

    👉 这导致 WebSocket 连接并未真正“运行”,Close Frame 无法被正确处理,所以回调不触发。


    ✅ 正确理解:SetCloseHandler 的工作机制

    conn.SetCloseHandler(...) 是告诉 gorilla/websocket 当收到对端发送的 WebSocket Close Frame 时要执行什么操作。

    但它 不会自动监听关闭帧 —— 它依赖于正在进行的 conn.ReadMessage() 调用!

    关键点:

    • 只有当你调用了 conn.ReadMessage()(或 NextReader()),底层才会去读网络数据。
    • 当对方关闭连接(如 Postman 点击 “Disconnect”),会发送一个 Close Frame。
    • ReadMessage() 在读到这个 Close Frame 时,才会触发你设置的 CloseHandler
    • 如果你不读,就永远收不到 Close Frame,也就不会触发回调。

    ✅ 修复后的完整示例代码

    package main
    
    import (
        "fmt"
        "net/http"
        "sync"
        "time"
    
        "github.com/gorilla/websocket"
    )
    
    var upgrader = websocket.Upgrader{
        CheckOrigin: func(r *http.Request) bool {
            return true // 允许所有跨域
        },
    }
    
    type WsChat struct{}
    
    func (ws *WsChat) WsChatCreateConn(operator_id int64, w http.ResponseWriter, r *http.Request) error {
        conn, err := upgrader.Upgrade(w, r, nil)
        if err != nil {
            return err
        }
        defer conn.Close()
    
        // 创建退出通道用于协调
        exitChan := make(chan struct{})
    
        var wg sync.WaitGroup
        wg.Add(2)
    
        // 启动写协程
        go func() {
            defer wg.Done()
            ws.WsChatMsgWrite(operator_id, conn, exitChan)
        }()
    
        // 启动读协程
        go func() {
            defer wg.Done()
            ws.WsChatMsgRead(operator_id, conn, exitChan)
        }()
    
        // 等待两个协程结束(比如其中一个出错或关闭)
        wg.Wait()
        fmt.Println("连接已关闭")
        return nil
    }
    
    // 读取消息:这是触发 CloseHandler 的关键!
    func (ws *WsChat) WsChatMsgRead(operator_id int64, conn *websocket.Conn, exitChan chan struct{}) {
        // 设置关闭处理器(必须在 Read 前设置!)
        conn.SetCloseHandler(func(code int, text string) error {
            fmt.Printf("收到关闭帧 - Code: %d, Text: %s\n", code, text)
    
            // 可选:发送一个关闭响应(Pong)
            conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(code, text))
    
            // 通知退出
            close(exitChan)
            return nil
        })
    
        for {
            mt, message, err := conn.ReadMessage() // 关键:只有 Read 才能收到 Close Frame
            if err != nil {
                if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
                    fmt.Printf("连接异常关闭: %v\n", err)
                } else {
                    fmt.Printf("连接正常关闭\n")
                }
                close(exitChan)
                return
            }
    
            fmt.Printf("收到消息 [%d]: %s\n", mt, message)
        }
    }
    
    // 写入消息示例
    func (ws *WsChat) WsChatMsgWrite(operator_id int64, conn *websocket.Conn, exitChan chan struct{}) {
        ticker := time.NewTicker(5 * time.Second)
        defer ticker.Stop()
    
        for {
            select {
            case <-ticker.C:
                err := conn.WriteMessage(websocket.TextMessage, []byte("心跳 "+time.Now().Format("15:04:05")))
                if err != nil {
                    fmt.Printf("写入失败: %v\n", err)
                    return
                }
            case <-exitChan:
                fmt.Println("写协程退出")
                return
            }
        }
    }
    

    🧩 关键修复点总结

    问题修复方式
    ❌ 没有读协程✅ 添加 WsChatMsgRead 并持续调用 ReadMessage()
    SetCloseHandler 放在写协程中✅ 移到读协程中,在 ReadMessage 前设置
    ❌ 主函数无限 Sleep✅ 使用 wg.Wait() 等待协程退出
    ❌ 缺少错误判断✅ 使用 websocket.IsUnexpectedCloseError 判断关闭类型

    ✅ 测试方法(Postman)

    1. 打开 Postman
    2. 新建 WebSocket 请求:ws://localhost:8080/your-path
    3. 发起连接
    4. 点击 “Disconnect” 按钮
    5. 查看服务端输出:
    收到关闭帧 - Code: 1001, Text: 
    连接正常关闭
    

    ✅ 成功触发!


    ⚠️ 注意事项

    • SetCloseHandler 只响应 对方主动关闭 发送的 Close Frame。
    • 如果客户端直接断网、崩溃或杀进程,不会发送 Close Frame → 触发不了回调 → 需靠 ReadMessage() 返回错误来检测。
    • 推荐结合 SetReadDeadline 做超时控制,防止连接长期挂起。

    ✅ 补充:如何主动关闭连接?

    如果你想从服务端主动关闭:

    conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
    

    然后 ReadMessage() 会返回错误,协程退出。


    ✅ 总结

    ❓ 为什么 SetCloseHandler 不触发?

    ✅ 因为你没有调用 ReadMessage()
    ✅ 必须有一个读协程持续运行,才能接收 Close Frame 并触发回调。

    📌 记住一句话:

    没有 Read,就没有 CloseHandler


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

报告相同问题?

问题事件

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