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循环里也不行,是使用上有问题么
golang gorillawebsocket关闭回调函数不起作用
- 写回答
- 好问题 0 提建议
- 关注问题
- 邀请回答
-
3条回答 默认 最新
Juan_2012 2025-10-23 18:59关注回答参考qwen
你提出的问题非常典型:**gorilla/websocket的SetCloseHandler回调函数没有被触发**,即使你在 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)
- 打开 Postman
- 新建 WebSocket 请求:
ws://localhost:8080/your-path - 发起连接
- 点击 “Disconnect” 按钮
- 查看服务端输出:
收到关闭帧 - 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”
本回答被题主选为最佳回答 , 对您是否有帮助呢?解决 无用评论 打赏 举报- 没有启动读取消息的 goroutine(虽然注释了