英文:
How to query TCP connection state in go?
问题
在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
}
但这并没有起到帮助作用。事实上,这导致我的连接更早关闭。我相当确定这是因为(在Mac上)这意味着在30秒后开始对连接进行健康探测,然后以30秒间隔进行8次探测。服务器端可能不支持保持活动状态,因此在4分30秒后,客户端会断开连接。
也许我无法使一个空闲连接无限期保持活动状态,如果有一种方式可以_检测_连接是否已关闭,以便我可以无缝地用新连接替换它,那将是完全可以接受的。然而,即使在阅读了所有文档并搜索了博客圈子以寻求帮助之后,我仍然找不到在Go中查询TCP连接状态的任何方法。
肯定有一种方法。有人对如何实现这一点有什么见解吗?非常感谢提前给予帮助的任何人!
编辑:
理想情况下,我希望学习如何使用纯Go处理这个问题,而不使用第三方库来实现。当然,如果有某个库可以做到这一点,我不介意被指向它,以便我可以看看它们是如何实现的。
英文:
On the client side of a TCP connection, I am attempting to to reuse established connections as much as possible to avoid the overhead of dialing every time I need a connection. Fundamentally, it's connection pooling, although technically, my pool size just happens to be one.
I'm running into a problem in that if a connection sits idle for long enough, the other end disconnects. I've tried using something like the following to keep connections alive:
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
}
But this isn't helping. In fact, it's causing my connections to close sooner. I'm pretty sure this is because (on a Mac) this means the connection health starts being probed after 30 seconds and then is probed at 8 times at 30 second intervals. The server side must not be supporting keepalive, so after 4 minutes and 30 seconds, the client is disconnecting.
There might be nothing I can do to keep an idle connection alive indefinitely, and that would be absolutely ok if there were some way for me to at least detect that a connection has been closed so that I can seamlessly replace it with a new one. Alas, even after reading all the docs and scouring the blogosphere for help, I can't find any way at all in go to query the state of a TCP connection.
There must be a way. Does anyone have any insight into how that can be accomplished? Many thanks in advance to anyone who does!
EDIT:
Ideally, I'd like to learn how to handle this, low-level with pure go-- without using third-party libraries to accomplish this. Of course if there is some library that does this, I don't mind being pointed in its direction so I can see how they do it.
答案1
得分: 5
socket API无法直接访问连接的状态。你可以通过各种方式从内核中查询当前状态(例如,在Linux上是/proc/net/tcp[6]
),但这并不能保证后续的发送操作会成功。
在这一点上我有点困惑。我的客户端只发送数据。除了确认数据包之外,服务器不会发送任何数据回来。读取似乎不是确定连接状态的合适方式,因为没有东西可读取。
Socket API的定义是通过读取返回0字节来检测关闭的连接。这就是它的工作方式。在Go中,这会被转换为Read返回io.EOF
。这通常是检测断开连接的最快方式。
那么我只需要发送数据并处理发生的任何错误吗?如果是这样,那就有问题了,因为我观察到当尝试在断开的管道上发送数据时,通常根本不会收到任何错误,这似乎完全错误。
如果你仔细观察TCP的工作原理,这就是预期的行为。如果远程端关闭了连接,那么你的第一个发送操作将触发服务器发送一个RST,完全关闭本地连接。你要么需要从连接中读取来检测关闭,要么如果你再次尝试发送数据,你将会收到一个错误(假设你等待足够长的时间以使数据包往返),例如在Linux上会收到"broken pipe"的错误。
澄清一下...我可以拨号,拔掉以太网线,仍然发送而不出错。显然消息无法传递,但我没有收到任何错误。
如果连接实际上已经断开,或者服务器完全无响应,那么你发送的数据包将被发送到无处。TCP堆栈无法区分真正缓慢的数据包、丢包、拥塞或者断开的连接。系统需要等待重传超时,并在失败之前尝试多次重传数据包。仅仅重试的标准配置可能需要13到30分钟才能触发一个错误。
在你的代码中,你可以:
- 打开keepalive。这将更快地通知你连接是否断开,因为空闲连接会被定期测试。
- 从socket中读取数据。要么使用并发的读取操作,要么使用select/poll/epoll来检查是否有数据可读(Go通常使用前者)。
- 设置超时(在Go中称为deadlines)。
如果你不期望从连接中接收任何数据,在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])
}
}
}()
英文:
The socket api doesn't give you access to the state of the connection. You can query the current state it in various ways from the kernel (/proc/net/tcp[6]
on linux for example), but that doesn't make any guarantee that further sends will succeed.
> I'm a little confused on one point here. My client is ONLY sending data. Apart from acking the packets, the server sends nothing back. Reading doesn't seem an appropriate way to determine connection status, as there's noting TO read.
The socket API is defined such that that you detect a closed connection by a read returning 0 bytes. That's the way it works. In Go, this is translated to a Read returning io.EOF
. This will usually be the fastest way to detect a broken connection.
> So am I supposed to just send and act on whatever errors occur? If so, that's a problem because I'm observing that I typically do not get any errors at all when attempting to send over a broken pipe-- which seems totally wrong
If you look closely at how TCP works, this is the expected behavior. If the connection is closed on the remote side, then your first send will trigger an RST from the server, fully closing the local connection. You either need to read from the connection to detect the close, or if you try to send again you will get an error (assuming you've waited long enough for the packets to make a round trip), like "broken pipe" on linux.
> To clarify... I can dial, unplug an ethernet cable, and STILL send without error. The messages don't get through, obviously, but I receive no error
If the connection is actually broken, or the server is totally unresponsive, then you're sending packets off to nowhere. The TCP stack can't tell the difference between packets that are really slow, packet loss, congestion, or a broken connection. The system needs to wait for the retransmission timeout, and retry the packet a number of times before failing. The standard configuration for retries alone can take between 13 and 30 minutes to trigger an error.
What you can do in your code is
- Turn on keepalive. This will notify you of a broken connection more quickly, because the idle connection is always being tested.
- Read from the socket. Either have a concurrent Read in progress, or check for something to read first with select/poll/epoll (Go usually uses the first)
- Set timeouts (deadlines in Go) for everything.
If you're not expecting any data from the connection, checking for a closed connection is very easy in Go; dispatch a goroutine to read from the connection until there's an error.
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])
}
}
}()
答案2
得分: 2
设计上,'TCP连接状态'并不存在。只有在发送数据时才会发生一些情况。在任何层次上,包括硅片级别,都没有TCP API可以告诉你TCP连接的当前状态。你必须尝试使用它。
如果你发送保持活动探测,服务器没有选择,只能适当地做出响应。服务器甚至不知道它们是保持活动探测。它们不是。它们只是重复的确认。支持保持活动只意味着支持发送保持活动探测。
英文:
-
There is no such thing as 'TCP connection state', by design. There is only what happens when you send something. There is no TCP API, at any level down to the silicon, that will tell you the current state of a TCP connection. You have to try to use it.
-
If you're sending keepalive probes, the server doesn't have any choice but to respond appropriately. The server doesn't even know that they are keepalives. They aren't. They are just duplicate ACKs. Supporting keepalive just means supporting sending keepalives.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论