目前问题是这样的如果发送的消息不是 OpText,OpBinary 这两种类型的话,连接会在 30 秒自动断开。
我定时不管是服务端还是客户端发送 opPing 也会断开。
只有一直定时发 OpText,OpBinary 这两种类型才不会断开。
看了源码也没有发现这个 30 秒在那里设置的。
这是抓包到断的最后几条记录..
// 这是服务端的代码 for { // set SetReadDeadline err := conn.SetReadDeadline(time.Time{}) if err != nil { logger.Errorf("SetReadDeadline failed: %s", err) // do something else, for example create new conn return } messages, err := wsutil.ReadClientMessage(client.conn, []wsutil.Message{}) if err != nil { // Read error logger.Infof("Websocket read error from client [%s] - %s", client.ID(), err) return } for _, msg := range messages { switch msg.OpCode { case ws.OpText, ws.OpBinary: if wh.srv.OnData== nil { logger.Errorf("websocket handler on data handler not setting, body: %s", string(msg.Payload)) return } if err = wh.srv.OnData(client, msg.Payload); err != nil { logger.Errorf("websocket handler data failure, body: %s err: %v", string(msg.Payload), err) } case ws.OpClose: // Close logger.Infof("receive client closed") case ws.OpPing: // Ping logger.Infof("receive client ping control message") if err := wsutil.WriteServerMessage(conn, ws.OpPong, nil); err != nil { logger.Errorf("send pong control message failure, err: %v", err) } case ws.OpContinuation: // Continuation logger.Infof("receive client continuation") case ws.OpPong: // Pong logger.Infof("receive pong....") default: // WTF -_-! logger.Errorf("receive error message from client: %s, op: %v", string(msg.Payload), msg.OpCode) } } } }()
// 重新贴下客户端代码 go func() { defer conn.Close() for { messages, err := wsutil.ReadServerMessage(conn, []wsutil.Message{}) if err != nil { logger.Infof("read error: %+v", err) return } logger.Infof("on receive message: %+v", messages) for _, msg := range messages { switch msg.OpCode { case ws.OpText, ws.OpBinary: if client.OnData != nil { err := client.OnData(client, msg.Payload) if err != nil { logger.Infof("on receive data error: %s", err) } } case ws.OpPing: logger.Infof("on receive ping message") _ = wsutil.WriteClientMessage(conn, ws.OpPong, nil) case ws.OpPong: logger.Infof("on receive pong message") case ws.OpClose: logger.Infof("on receive close message") return } } } }() go func() { ticker := time.NewTicker(pingPeriod) defer func() { ticker.Stop() _ = conn.Close() }() for { select { case <-ticker.C: logger.Infof("ticker .......") // _ = conn.SetWriteDeadline(time.Now().Add(writeWait)) //cmd := message.GenerateSimpleCommand( // message.CommandPing, //) err := wsutil.WriteClientMessage(conn, ws.OpPing, nil) if err != nil { logger.Infof("ticker write ....... %+v", err) break } } } }()
1 hxzhouh1 2024-05-20 19:06:17 +08:00 有没有可能是防火墙干的? 抓包看看? |
2 hxzhouh1 2024-05-20 19:06:56 +08:00 有没有可能是防火墙干的? 抓包看看? @hxzhouh1 我遇到过某个环境,网关/防火墙 会 90s 定时把 stream 断掉 |
3 OneMan 2024-05-20 19:12:08 +08:00 排除法,防火墙检查,换另外服务端测试,换另外客户端测试。 目测可能是服务端代码,有 30 秒的检查 |
6 david98 2024-05-20 19:38:45 +08:00 可以抓一下包 配置成明文的 websocket 信道 可以看看到底是哪里出的问题 |
![]() | 7 kuanat 2024-05-20 19:57:47 +08:00 via Android 我没有用过这个库,随便猜测一下。 理论上 ws 这种应用层协议,没有主动关闭行为,是不会在自己层面关闭连接的。底层的 tcp 在没有 keepalive 介入的情况下,连接建立后能够无限保持。ws 库在收到关闭信号之后,会向更底层传递这个信号,于是 http 到 tcp 都会关闭相应 socket 。 上面的意思是,这个行为不是 ws 库和你的程序主动行为造成的。 我看到你说有定时发送 ping ,那么另一端是否有回应 pong 呢?如果没有回应的话会出现一种情况,接收方会保持正常,而发送方连续 30s 只有发送而没有接收,触发了更底层协议的某个断开机制。 正好 golang net/http 默认 transport 超时就是 30s 。如果上面的库是基于标准库实现的话,可能就是 http 层先断开了。 |
![]() | 9 Ipsum 2024-05-20 20:38:12 +08:00 via Android 本地测试,如果没有断估计就是防火墙问题了。有些防火墙为了节省资源空闲 90s 就强制断 |
10 hellodudu86 2024-05-20 20:44:03 +08:00 听着像 timeout deadline 之类的问题 |
![]() | 11 meshell OP @hellodudu86 目录这个只有 read deadline, 和 write deadline 这两个没有设置的。 |
12 Ericcccccccc 2024-05-20 20:49:07 +08:00 30s 这种很像是保活的问题,你把框架的参数一个一个拿出来仔细看看。 |
13 hellodudu86 2024-05-20 21:02:42 +08:00 我一般的排查思路是,先确定是客户端还是服务器主动断开,这一点可以在 conn 的 read 或者 write 接口调用返回时打印 err 得知。然后固定 30 秒就断开非常像设置了 conn 的 read 或者 write deadline ,也很有可能是传递上下文的 context 设置了 30 秒的超时,建议重点查下这两块地方。 |
14 tairan2006 2024-05-20 21:10:04 +08:00 可以加个应用层心跳 debug 的话,你要看一下整个网络链路,比如是不是中间的 LB 把连接给断了…… |
![]() | 15 meshell OP @hellodudu86 特意看了 context 这个 context.Background()这个是没有超时的。read 都是设置的 是 0 ,write 都没有设置。。。我都要崩溃了。。。 |
![]() | 16 meshell OP @tairan2006 ping,pong 就是吧。还是 ? |
![]() | 17 meshell OP @kuanat 大佬没有看到你说得这个 。。“ 正好 golang net/http 默认 transport 超时就是 30s 。”,关键我也不是用得 http.client |
![]() | 18 cgtx 2024-05-20 22:14:37 +08:00 小王,我是张总,这个问题你都要上 v2 来问,昨天你给我的保证让我很不能信服啊。明天来办一下离职手续吧。 |
![]() | 19 wwqgtxx 2024-05-20 22:20:00 +08:00 个人建议你写一个最小复现代码挂 gist 上让大家伙试试 |
![]() | 20 mango88 2024-05-20 22:28:24 +08:00 用 github 给的 server 示例,没复现你的问题 |
![]() | 23 kuanat 2024-05-20 22:56:38 +08:00 ![]() @meshell #17 你给的截图里,最后一次客户端 ACK 确认服务端 Pong 之后,服务端主动发送 FIN ,说明断开是服务端的行为。 这个断开没有 opClose ,说明不是你的程序、也不是 ws 库的行为。 因为你是本地测试,也不会涉及防火墙。 由于 Ping/Pong 的间隔是 2s ,有双向通信,说明不是 Idle 相关的超时。也就是说,并不是 KeepAlive 等机制触发的先断开底层,再断开上层。 整个协议层面,在 ws 之下,还有(大概率)标准库 net/http ,再下层就是系统的 tcp socket 了。 我记忆中标准库 DefaultTransport 有个 30s 超时,查了一下 https://go.dev/src/net/http/transport.go 确实有,但是应该与你的问题无关。 正好你说你用的不是 http.client ,可以贴一下最小可复现的完整代码。因为之前的代码看不到 conn 的来源,可能是有哪个地方设置了超时参数。 |
![]() | 24 meshell OP @kuanat ```golang func (wh *wsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { conn, _, _, err := ws.UpgradeHTTP(r, w) if err != nil { logger.Errorf("Websocket upgrade failed : %s", err) return } client := &Client{ conn: conn, // fd: nfd(conn), server: wh.srv, id: uuid.New(), } logger.Infof("Client [%s] connected to [%s] as [%s]", conn.RemoteAddr(), conn.LocalAddr(), client.id) wh.srv.Lock() wh.srv.clients[client.id] = client wh.srv.Unlock() if wh.srv.OnConnect != nil { wh.srv.OnConnect(client) } // ... 下面的代码就是上面发的。这里的代码就是调用 ws 库,升级成 websocket.. 拿到链接. } ``` |
25 jioswu 2024-05-20 23:46:42 +08:00 我好像也遇到过这个问题,蹲一个解答 哈哈哈 |
![]() | 26 kuanat 2024-05-20 23:54:19 +08:00 @meshell #24 继续往下查吧,wh.srv 可能做了些什么操作。随便猜一下,可能是某个 context 有 30s 的设置,超时之后直接在 http 层面触发了 defer Close() 之类的操作,这个操作完成了 tcp 层面 FIN/ACK 的关闭,结果导致 ws 层面是没有 opClose 消息的。 我看了一下 gobwas/ws 的代码,UpgradeHTTP 这里就把 net.Conn 的 deadline 给清除了(设置了 time.Time 的零值)。(既然是无超时,理论上每次读 message 的时候 err := conn.SetReadDeadline(time.Time{}) 这个重置就没有必要了,不过与你的问题无关) |
![]() | 27 tywtyw2002 2024-05-21 08:06:34 +08:00 via iPhone 你先测试下 15s pingpong 会不会断,如果不断 大概率就是他们说的 30s http 层的问题。 如果 15s 也断,那就一层层看代码往上找吧,最后走到 socket 层。 |
![]() | 28 meshell OP ![]() @kuanat 大佬结案了 。。。。特默代码其它地方的问题。。。其它地方有定时器。。一开始没有没有仔细去看完整代码。。。只管实现了 。。。 |
![]() | 29 meshell OP @tywtyw2002 结案了。 |
![]() | 30 lasuar 2024-05-21 10:11:08 +08:00 在它的代码库搜 ‘30’ 或者 Second 挨个看。 |
![]() | 31 lasuar 2024-05-21 10:14:13 +08:00 现在很多 web 框架也内置了 ws ,比如 gin iris ,或者可以使用经典的 gorilla/websocket 库避免一些低级 bug ,对于这种基础网络协议,不要去找那些几 kstar 的库。 测试习惯是很好的,保持。 |