在 TCP 连接的客户端,我尝试尽可能地重用已建立的连接,以避免每次需要连接时进行拨号的开销。从根本上来说,它是连接池,尽管从技术上讲,我的池大小恰好是一。
我遇到的问题是,如果连接空闲足够长的时间,另一端就会断开连接。我尝试使用类似以下内容来保持连接处于活动状态:
err = conn.(*net.TCPConn).SetKeepAlive(true)
if err != nil {
fmt.Println(err)
return
}
err = conn.(*net.TCPConn).SetKeepAlivePeriod(30*time.Second)
if err != nil {
fmt.Println(err)
return
}
Run Code Online (Sandbox Code Playgroud)
但这没有帮助。事实上,它导致我的连接更快关闭。我很确定这是因为(在 Mac 上)这意味着在 30 秒后开始探测连接运行状况,然后以 30 秒的间隔探测 8 次。 服务器端一定不支持 keepalive,所以在 4 分 30 秒后,客户端断开连接。
我可能无法做任何事情来无限期地保持空闲连接,如果有某种方法让我至少检测到连接已关闭,以便我可以无缝地用新连接替换它,那绝对没问题。唉,即使阅读了所有文档并在博客圈寻求帮助后,我在 go 中也找不到任何方法来查询 TCP 连接的状态。
一定有办法的。有谁知道如何实现这一点?非常感谢任何这样做的人!
编辑:
理想情况下,我想学习如何用纯 go 来处理这个低级问题,而不使用第三方库来完成这个任务。当然,如果有一些图书馆可以做到这一点,我不介意被指出它的方向,这样我就可以看到他们是如何做到这一点的。
套接字 api 不允许您访问连接状态。您可以通过内核以各种方式查询当前状态(/proc/net/tcp[6]例如在 Linux 上),但这并不能保证进一步的发送会成功。
我对这里的某一点有点困惑。我的客户端仅发送数据。除了确认数据包之外,服务器不发送任何信息。读取似乎不是确定连接状态的适当方法,因为有注释 TO read。
套接字 API 的定义使得您可以通过返回 0 字节的读取来检测关闭的连接。这就是它的工作原理。在 Go 中,这被转换为 Read 返回io.EOF。这通常是检测连接断开的最快方法。
那么我是否应该发送并处理发生的任何错误?如果是这样,那就是一个问题,因为我观察到,当尝试通过损坏的管道发送时,我通常根本不会收到任何错误 - 这似乎完全错误
如果您仔细观察 TCP 的工作原理,就会发现这是预期的行为。如果连接在远程端关闭,那么您的第一次发送将触发来自服务器的 RST,完全关闭本地连接。您要么需要从连接中读取数据以检测关闭,要么如果您尝试再次发送,您将收到错误(假设您已经等待足够长的时间让数据包进行往返),就像 Linux 上的“管道损坏” 。
为了澄清...我可以拨号,拔掉以太网电缆,并且仍然可以毫无错误地发送。显然,消息没有通过,但我没有收到任何错误
如果连接确实中断,或者服务器完全没有响应,那么您将数据包发送到任何地方。TCP 堆栈无法区分非常慢的数据包、数据包丢失、拥塞或连接中断。系统需要等待重传超时,并在失败之前重试数据包多次。仅重试的标准配置可能需要 13 到 30 分钟才能触发错误。
你可以在你的代码中做的是
如果您不希望从连接中获得任何数据,那么在 Go 中检查关闭的连接非常容易;调度一个 goroutine 从连接中读取数据,直到出现错误。
notify := make(chan error)
go func() {
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil {
notify <- err
return
}
if n > 0 {
fmt.Println("unexpected data: %s", buf[:n])
}
}
}()
Run Code Online (Sandbox Code Playgroud)