目前问题是这样的如果发送的消息不是 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 212 天前
有没有可能是防火墙干的? 抓包看看?
|
3
OneMan 212 天前
排除法,防火墙检查,换另外服务端测试,换另外客户端测试。
目测可能是服务端代码,有 30 秒的检查 |
6
david98 212 天前
可以抓一下包 配置成明文的 websocket 信道 可以看看到底是哪里出的问题
|
7
kuanat 212 天前 via Android
我没有用过这个库,随便猜测一下。
理论上 ws 这种应用层协议,没有主动关闭行为,是不会在自己层面关闭连接的。底层的 tcp 在没有 keepalive 介入的情况下,连接建立后能够无限保持。ws 库在收到关闭信号之后,会向更底层传递这个信号,于是 http 到 tcp 都会关闭相应 socket 。 上面的意思是,这个行为不是 ws 库和你的程序主动行为造成的。 我看到你说有定时发送 ping ,那么另一端是否有回应 pong 呢?如果没有回应的话会出现一种情况,接收方会保持正常,而发送方连续 30s 只有发送而没有接收,触发了更底层协议的某个断开机制。 正好 golang net/http 默认 transport 超时就是 30s 。如果上面的库是基于标准库实现的话,可能就是 http 层先断开了。 |
9
Ipsum 212 天前 via Android
本地测试,如果没有断估计就是防火墙问题了。有些防火墙为了节省资源空闲 90s 就强制断
|
10
hellodudu86 212 天前
听着像 timeout deadline 之类的问题
|
11
meshell OP @hellodudu86 目录这个只有 read deadline, 和 write deadline 这两个没有设置的。
|
12
Ericcccccccc 212 天前
30s 这种很像是保活的问题,你把框架的参数一个一个拿出来仔细看看。
|
13
hellodudu86 212 天前
我一般的排查思路是,先确定是客户端还是服务器主动断开,这一点可以在 conn 的 read 或者 write 接口调用返回时打印 err 得知。然后固定 30 秒就断开非常像设置了 conn 的 read 或者 write deadline ,也很有可能是传递上下文的 context 设置了 30 秒的超时,建议重点查下这两块地方。
|
14
tairan2006 212 天前
可以加个应用层心跳
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 212 天前
小王,我是张总,这个问题你都要上 v2 来问,昨天你给我的保证让我很不能信服啊。明天来办一下离职手续吧。
|
19
wwqgtxx 212 天前
个人建议你写一个最小复现代码挂 gist 上让大家伙试试
|
20
mango88 212 天前
用 github 给的 server 示例,没复现你的问题
|
23
kuanat 212 天前 1
@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 211 天前
我好像也遇到过这个问题,蹲一个解答 哈哈哈
|
26
kuanat 211 天前
@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 211 天前 via iPhone
你先测试下 15s pingpong 会不会断,如果不断 大概率就是他们说的 30s http 层的问题。
如果 15s 也断,那就一层层看代码往上找吧,最后走到 socket 层。 |
29
meshell OP @tywtyw2002 结案了。😭
|
30
lasuar 211 天前
在它的代码库搜 ‘30’ 或者 Second 挨个看。
|
31
lasuar 211 天前
现在很多 web 框架也内置了 ws ,比如 gin iris ,或者可以使用经典的 gorilla/websocket 库避免一些低级 bug ,对于这种基础网络协议,不要去找那些几 kstar 的库。
测试习惯是很好的,保持。 |