func (c *Client) ReadPump() error { defer func() { // On exit, unregister the client and close the connection // send c to unregister channel // c.session.Close() // close(c.send) // close the session // close the connection // c.conn.Close() log.Println("ws-conn: close readPump!") }() c.Conn.SetReadDeadline(time.Now().Add(60 * time.Second)) c.Conn.SetPongHandler(func(appData string) error { c.Conn.SetReadDeadline(time.Now().Add(60 * time.Second)) return nil }) c.Session.LLMConsumer() c.Session.MonitorSubSize() for { select { // case <-c.session.Done: // log.Println("Session processing done, exiting readPump.") // return case <-c.ctx.Done(): log.Println("ws-conn: ws-conn: Client context done, exiting readPump.") return c.ctx.Err() case <-c.Session.ctx.Done(): log.Println("Session is closed, exiting readPump.") return nil default: // broadcast the received message to all clients // c.hub.broadcast <- message } msgType, message, err := c.Conn.ReadMessage() }
对,你观察得很准确:
ReadPump() 里的核心调用:
ReadPump()
msgType, message, err := c.Conn.ReadMessage()
是 阻塞的。也就是说,如果没有数据到来,它会一直卡在这一行。
c.ctx.Done()
c.Session.ctx.Done()
select
<-c.ctx.Done()
<-c.Session.ctx.Done()
ReadMessage()
default
<-ctx.Done()
c.Conn.SetReadDeadline(time.Now().Add(60 * time.Second)) c.Conn.SetPongHandler(func(string) error { c.Conn.SetReadDeadline(time.Now().Add(60 * time.Second)) return nil })
ctx.Done()
use of closed network connection
把 ReadMessage() 放到独立 goroutine,然后 select 等待:
readCh := make(chan struct { msgType int msg []byte err error }) go func() { mt, msg, err := c.Conn.ReadMessage() readCh <- struct { msgType int msg []byte err error }{mt, msg, err} }() select { case <-c.ctx.Done(): c.Conn.Close() // 中断阻塞 return c.ctx.Err() case <-c.Session.ctx.Done(): c.Conn.Close() return nil case r := <-readCh: if r.err != nil { return r.err } // 处理 r.msg }
这样就不会被阻塞的 ReadMessage() 卡住了。
最简单、也是 Go WebSocket 通常做法:
c.Conn.SetReadDeadline(time.Now().Add(time.Second)) // 每秒检查 for { select { case <-c.ctx.Done(): c.Conn.Close() // 立即中断 ReadMessage return c.ctx.Err() default: _, _, err := c.Conn.ReadMessage() if err != nil { return err } } }
阻塞在 ReadMessage() 时,select <-ctx.Done() 不会立即生效
select <-ctx.Done()
必须:
Conn.Close()
SetReadDeadline
PongHandler
直接依赖 <-ctx.Done() 不足以立即退出
如果你需要,我可以帮你改写你的 ReadPump(),做到:
你希望我帮你写吗?
Github开源生信云平台 DEMO