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里的关闭帧触发逻辑,只有样例三的关闭回调函数可以被触发(跟样例1、2的区别是放入了ReadMessage读取,并且关闭回调函数设置在前),样例1和2都触发不了,有了解原理的么
golang websocket读取和关闭帧的关系
- 写回答
- 好问题 0 提建议
- 关注问题
- 邀请回答
-
3条回答 默认 最新
关注让【宝码香车】来帮您解答,本回答参考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)) } }()关键要点
- 关闭回调必须在读取操作之前设置
- 必须调用
ReadMessage()才能触发关闭处理 - 关闭帧是作为特殊消息在读取时被处理的
- 建议在连接建立后立即设置关闭处理器
这种设计确保了 WebSocket 协议的完整性,只有在主动读取消息时才会处理协议级别的控制帧(包括关闭帧)。
本回答被题主选为最佳回答 , 对您是否有帮助呢?解决 无用评论 打赏 举报