lesismal / nbio

Pure Go 1000k+ connections solution, support tls/http1.x/websocket and basically compatible with net/http, with high-performance and low memory cost, non-blocking, event-driven, easy-to-use.
MIT License
2.11k stars 151 forks source link

请教个问题,客户端明明立马可以收到消息,但是报了写超时的错误 #438

Closed dmzlingyin closed 3 weeks ago

dmzlingyin commented 3 weeks ago
func (s *Server) newUpgrader() *websocket.Upgrader {
    u := websocket.NewUpgrader()
    u.SetPingHandler(func(c *websocket.Conn, appData string) {
        if err := c.WriteMessage(websocket.PongMessage, nil); err != nil {
            s.logger.Errorf("failed to send pong message to client: %v", err)
        }
    })
    u.OnOpen(func(c *websocket.Conn) {
        s.logger.Debugf("OnOpen: %s", c.RemoteAddr().String())
    })
    u.OnMessage(func(c *websocket.Conn, messageType websocket.MessageType, data []byte) {
        switch messageType {
        case websocket.BinaryMessage:
            msg := &contract.Message{}
            if err := proto.Unmarshal(data, msg); err != nil {
                s.logger.Errorf("failed to unmarshal binary message: %s", err)
                return
            }
            if h, ok := s.handlers[msg.Type]; ok {
                if err := h(msg); err != nil {
                    s.logger.Errorf("failed to handle message, op: %v, err: %s", messageType, err)
                }
            }
        default:
            s.logger.Errorf("got unexpect message type: %v", messageType)
        }
    })
    u.OnClose(func(c *websocket.Conn, err error) {
        s.logger.Debugf("OnClose: %s, err: %s", c.RemoteAddr().String(), err)
        s.sm.Remove(c)
    })
    return u
}

上面是upgrader逻辑

func (s *Session) Start() (err error) {
    for {
        select {
        case msg := <-s.Messages:
            if err = s.SetWriteDeadline(time.Now().Add(writeTimeout)); err != nil {
                if msg.g != nil {
                    msg.g.Remove(s)
                }
                return
            }
            if err = s.WriteMessage(websocket.BinaryMessage, msg.data); err != nil {
                if msg.g != nil {
                    msg.g.Remove(s)
                }
                return
            }
        case <-s.Stop:
            return
        }
    }
}

上面是我们业务侧发送消息的代码,首先设置了10s的写超时时间,然后进行发送。有两个比较奇怪的问题:

  1. 客户端隔一段时间ping服务端。服务端向客户端发送消息后,客户端立马收到了消息,但是到了写超时时间,服务端进入了OnClose函数,错误原因为:err: write tcp [::1]:8080->[::1]:60682: i/o timeout
  2. 客户端不ping服务端,这就会导致120s后,连接断开。但是这种情况下,服务端向客户端发送消息,是不会报写超时的

请问一下,这是什么问题呢

lesismal commented 3 weeks ago

感谢使用和反馈!

这里session相关的实现和具体的流程不全, 能提供个完整的能复现的简单例子吗我跑下试试

dmzlingyin commented 3 weeks ago

https://github.com/dmzlingyin/gateway-demo

我把核心代码抽离出来了,麻烦大佬看一下。

如果发送消息的频率大于超时时间,就不会有问题。如果频率小于超时时间,就报错:err: write tcp [::1]:8080->[::1]:60682: i/o timeout

lesismal commented 3 weeks ago

这个没有客户端,我没法跑起来去调试错误。 单看代码,这里有没有可能设置了SetWriteDeadline后没有执行WriteMessage? https://github.com/dmzlingyin/gateway-demo/blob/main/server/session.go#L51

建议本地调试或者加日志先看下,可以调试的话看看进入WriteMessage没,如果进入了,后续的write成功没

lesismal commented 3 weeks ago

另外,这种遍历map的没有加锁,估计还会有其他地方并发写吧,那这样都是可能导致宕机的: https://github.com/dmzlingyin/gateway-demo/blob/main/server/server.go#L67

dmzlingyin commented 3 weeks ago

这个没有客户端,我没法跑起来去调试错误。 单看代码,这里有没有可能设置了SetWriteDeadline后没有执行WriteMessage? https://github.com/dmzlingyin/gateway-demo/blob/main/server/session.go#L51

建议本地调试或者加日志先看下,可以调试的话看看进入WriteMessage没,如果进入了,后续的write成功没

webcosket-test.py可以当做客户端调试。

我打印过日志,SetWriteDeadline执行成功了,WriteMessage也执行成功了,客户端也立马收到消息了。

dmzlingyin commented 3 weeks ago

另外,这种遍历map的没有加锁,估计还会有其他地方并发写吧,那这样都是可能导致宕机的: https://github.com/dmzlingyin/gateway-demo/blob/main/server/server.go#L67

目前业务里面还没其他并发写的地方,我就先暂时简单处理了

lesismal commented 3 weeks ago

SetWriteDeadline之后如果没有更新并且已经超时, 那么下次任意写都会导致写失败, 因为已经到期了. 所以每次写之前都应该SetWriteDeadline重置这个时间, 我在这里WriteMessage之前加上试了下, 就没有超时了: https://github.com/dmzlingyin/gateway-demo/blob/main/server/server.go#L79

dmzlingyin commented 3 weeks ago

SetWriteDeadline之后如果没有更新并且已经超时, 那么下次任意写都会导致写失败, 因为已经到期了. 所以每次写之前都应该SetWriteDeadline重置这个时间, 我在这里WriteMessage之前加上试了下, 就没有超时了: https://github.com/dmzlingyin/gateway-demo/blob/main/server/server.go#L79

问题已经解决了,非常感谢大佬的指正!!!