C语言 有没有办法检测 TCP 套接字已被远程对等方关闭,而无需从中读取?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17705239/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a way to detect that TCP socket has been closed by the remote peer, without reading from it?
提问by Jeremy Friesner
First, a little background to explain the motivation: I'm working on a very simple select()-based TCP "mirror proxy", that allows two firewalled clients to talk to each other indirectly. Both clients connect to this server, and as soon as both clients are connected, any TCP bytes sent to the server by client A is forwarded to client B, and vice-versa.
首先,解释一下动机的一些背景知识:我正在研究一个非常简单的基于 select() 的 TCP“镜像代理”,它允许两个受防火墙保护的客户端间接地相互交谈。两个客户端都连接到此服务器,并且一旦两个客户端都连接,客户端 A 发送到服务器的任何 TCP 字节都会转发到客户端 B,反之亦然。
This more or less works, with one slight gotcha: if client A connects to the server and starts sending data before client B has connected, the server doesn't have anywhere to put the data. I don't want to buffer it up in RAM, since that could end up using a lot of RAM; and I don't want to just drop the data either, as client B might need it. So I go for the third option, which is to not select()-for-read-ready on client A's socket until client B has also connected. That way client A just blocks until everything is ready to go.
这或多或少有效,但有一个小问题:如果客户端 A 连接到服务器并在客户端 B 连接之前开始发送数据,则服务器没有任何地方可以放置数据。我不想将它缓存在 RAM 中,因为这最终可能会使用大量 RAM;我也不想删除数据,因为客户端 B 可能需要它。所以我选择第三个选项,即在客户端 B 也连接之前不在客户端 A 的套接字上选择()-for-read-ready。这样客户端 A 就会阻塞,直到一切准备就绪。
That more or less works too, but the side effect of not selecting-for-read-ready on client A's socket is that if client A decides to close his TCP connection to the server, the server doesn't get notified about that fact -- at least, not until client B comes along and the server finally selects-for-read-ready on client A's socket, reads any pending data, and then gets the socket-closed notification (i.e. recv() returning 0).
这或多或少也有效,但是在客户端 A 的套接字上不选择读取就绪的副作用是,如果客户端 A 决定关闭与服务器的 TCP 连接,则服务器不会收到有关该事实的通知 - - 至少,直到客户端 B 出现并且服务器最终在客户端 A 的套接字上选择读取就绪,读取任何挂起的数据,然后获得套接字关闭通知(即 recv() 返回 0)。
I'd prefer it if the server had some way of knowing (in a timely manner) when client A closed his TCP connection. Is there a way to know this? Polling would be acceptable in this case (e.g. I could have select() wake up once a minute and call IsSocketStillConnected(sock) on all sockets, if such a function existed).
如果服务器有某种方式(及时地)知道客户端 A 何时关闭了他的 TCP 连接,我会更喜欢它。有没有办法知道这一点?在这种情况下轮询是可以接受的(例如,如果存在这样的函数,我可以让 select() 每分钟唤醒一次并在所有套接字上调用 IsSocketStillConnected(sock) )。
采纳答案by Jeremy Friesner
It appears the answer to my question is "no, not unless you are willing and able to modify your TCP stack to get access to the necessary private socket-state information".
我的问题的答案似乎是“不,除非您愿意并能够修改 TCP 堆栈以访问必要的私有套接字状态信息”。
Since I'm not able to do that, my solution was to redesign the proxy server to always read data from all clients, and throw away any data that arrives from a client whose partner hasn't connected yet. This is non-optimal, since it means that the TCP streams going through the proxy no longer have the stream-like property of reliable in-order delivery that TCP-using programs expect, but it will suffice for my purpose.
由于我无法做到这一点,我的解决方案是重新设计代理服务器以始终从所有客户端读取数据,并丢弃来自合作伙伴尚未连接的客户端的任何数据。这是非最佳的,因为这意味着通过代理的 TCP 流不再具有使用 TCP 的程序所期望的类似流的可靠有序交付的属性,但这足以满足我的目的。
回答by jxh
If you want to check if the socket is actually closed instead of data, you can add the MSG_PEEKflag on recv()to see if data arrived or if you get 0or an error.
如果您想检查套接字是否实际关闭而不是数据,您可以添加MSG_PEEK标志recv()以查看数据是否到达或者是否收到0或错误。
/* handle readable on A */
if (B_is_not_connected) {
char c;
ssize_t x = recv(A_sock, &c, 1, MSG_PEEK);
if (x > 0) {
/* ...have data, leave it in socket buffer until B connects */
} else if (x == 0) {
/* ...handle FIN from A */
} else {
/* ...handle errors */
}
}
Even if A closes after sending some data, your proxy probably wants to forward that data to B first before forwarding the FIN to B, so there is no point in knowing that A has sent FIN on the connection sooner than after having read all the data it has sent.
即使 A 在发送一些数据后关闭,您的代理也可能希望在将 FIN 转发给 B 之前先将该数据转发给 B,因此知道 A 在连接上发送 FIN 比读取所有数据后更早是毫无意义的它已经发送。
A TCP connection isn't considered closed until after both sides send FIN. However, if A has forcibly shutdown its endpoint, you will not know that until after you attempt to send data on it, and receive an EPIPE(assuming you have suppressed SIGPIPE).
直到双方发送 FIN 之后,TCP 连接才会被视为关闭。但是,如果 A 强行关闭了它的端点,直到您尝试在其上发送数据并收到一个EPIPE(假设您已抑制SIGPIPE)之后,您才会知道这一点。
After reading your mirror proxy application a bit more, since this is a firewall traversal application, it seems that you actually need a small control protocol to allow to you verify that these peers are actually allowed to talk to each other. If you have a control protocol, then you have many solutions available to you, but the one I would advocate would be to have one of the connections describe itself as the server, and the other connection describe itself as the client. Then, you can reset the connection the client if there is no server present to take its connection. You can let servers wait for a client connection up to some timeout. A server should not initiate any data, and if it does without a connected client, you can reset the server connection. This eliminates the issue of buffering data for a dead connection.
多读一点你的镜像代理应用程序后,由于这是一个防火墙穿越应用程序,看来你实际上需要一个小的控制协议来允许你验证这些对等点实际上是被允许相互交谈的。如果你有一个控制协议,那么你有很多可用的解决方案,但我提倡的是让一个连接将自己描述为服务器,另一个连接将自己描述为客户端。然后,如果没有服务器存在来获取其连接,您可以重置客户端的连接。您可以让服务器等待客户端连接到一定的超时时间。服务器不应启动任何数据,如果它在没有连接的客户端的情况下启动,您可以重置服务器连接。这消除了为死连接缓冲数据的问题。
回答by Random Profile
For me the solution was to poll the socket status.
对我来说,解决方案是轮询套接字状态。
On Windows 10, the following code seemed to work (but equivalent implementations seem to exist for other systems):
在 Windows 10 上,以下代码似乎有效(但其他系统似乎存在等效的实现):
WSAPOLLFD polledSocket;
polledSocket.fd = socketItf;
polledSocket.events = POLLRDNORM | POLLWRNORM;
if (WSAPoll(&polledSocket, 1, 0) > 0)
{
if (polledSocket.revents &= (POLLERR | POLLHUP))
{
// socket closed
return FALSE;
}
}
回答by ja_mesa
I don't see the problem as you see it. Let's say A connects to the server sends some data and close, it does not need any message back. Server won't read its data until B connects, once it does server read socket A and send the data to B. The first read will return the data A had sent and the second return either 0 or -1 in either case the socket is closed, server close B. Let's suppose A send a big chunk of data, the A's send() method will block until server starts reading and consumes the buffer.
我没有看到你看到的问题。假设 A 连接到服务器发送一些数据并关闭,它不需要任何消息。服务器在 B 连接之前不会读取它的数据,一旦它服务器读取套接字 A 并将数据发送到 B。第一次读取将返回 A 发送的数据,第二次返回 0 或 -1,在这两种情况下套接字是关闭,服务器关闭 B。假设 A 发送一大块数据,A 的 send() 方法将阻塞,直到服务器开始读取并消耗缓冲区。
I would use a function with a select which returns 0, 1, 2, 11, 22 or -1, where;
我会使用一个带有选择的函数,它返回 0、1、2、11、22 或 -1,其中;
- 0=No data in either socket (timeout)
- 1=A has data to read
- 2=B has data to read
- 11=A socket has an error (disconnected)
- 22=B socket has an error (disconnected)
-1: One/both socket is/are not valid
int WhichSocket(int sd1, int sd2, int seconds, int microsecs) { fd_set sfds, efds; struct timeval timeout={0, 0}; int bigger; int ret; FD_ZERO(&sfds); FD_SET(sd1, &sfds); FD_SET(sd2, &sfds); FD_SET(sd1, &efds); FD_SET(sd2, &efds); timeout.tv_sec=seconds; timeout.tv_usec=microsecs; if (sd1 > sd2) bigger=sd1; else bigger=sd2; // bigger is necessary to be Berkeley compatible, Microsoft ignore this param. ret = select(bigger+1, &sfds, NULL, &efds, &timeout); if (ret > 0) { if (FD_ISSET(sd1, &sfds)) return(1); // sd1 has data if (FD_ISSET(sd2, &sfds)) return(2); // sd2 has data if (FD_ISSET(sd1, &efds)) return(11); // sd1 has an error if (FD_ISSET(sd2, &efds)) return(22); // sd2 has an error } else if (ret < 0) return -1; // one of the socket is not valid return(0); // timeout }
- 0=任一套接字中都没有数据(超时)
- 1=A 有数据要读取
- 2=B 有数据要读取
- 11=一个套接字有错误(断开连接)
- 22=B 套接字有错误(断开连接)
-1:一个/两个套接字无效
int WhichSocket(int sd1, int sd2, int seconds, int microsecs) { fd_set sfds, efds; struct timeval timeout={0, 0}; int bigger; int ret; FD_ZERO(&sfds); FD_SET(sd1, &sfds); FD_SET(sd2, &sfds); FD_SET(sd1, &efds); FD_SET(sd2, &efds); timeout.tv_sec=seconds; timeout.tv_usec=microsecs; if (sd1 > sd2) bigger=sd1; else bigger=sd2; // bigger is necessary to be Berkeley compatible, Microsoft ignore this param. ret = select(bigger+1, &sfds, NULL, &efds, &timeout); if (ret > 0) { if (FD_ISSET(sd1, &sfds)) return(1); // sd1 has data if (FD_ISSET(sd2, &sfds)) return(2); // sd2 has data if (FD_ISSET(sd1, &efds)) return(11); // sd1 has an error if (FD_ISSET(sd2, &efds)) return(22); // sd2 has an error } else if (ret < 0) return -1; // one of the socket is not valid return(0); // timeout }
回答by SKi
If your proxy must be a general purpose proxy for any protocol, then you should handle also those clients which sends data and immediately calls closeafter the send (one way data transfer only).
如果您的代理必须是任何协议的通用代理,那么您还应该处理那些发送数据并close在发送后立即调用的客户端(仅限单向数据传输)。
So if client A sends a data and closes the connection before the connection is opened to B, don't worry, just forward the data to B normally (when connection to B is opened).
所以如果客户端A在给B打开连接之前发送了一个数据并关闭了连接,不用担心,只需将数据正常转发给B即可(当连接B打开时)。
There is no need to implement special handling for this scenario.
无需为此场景实施特殊处理。
Your proxy will detect the closed connection when:
在以下情况下,您的代理将检测关闭的连接:
readreturns zero after connection to B is opened and all pending data from A is read. or- your programs try to send data (from B) to A.
read在打开与 B 的连接并读取来自 A 的所有挂起数据后返回零。或者- 您的程序尝试将数据(从 B)发送到 A。
回答by Optox
You could check if the socket is still connected by trying to write to the file descriptor for each socket. Then if the return value of the write is -1 or if errno = EPIPE, you know that socket has been closed.
for example
您可以通过尝试写入每个套接字的文件描述符来检查套接字是否仍然连接。那么如果write的返回值是-1或者errno = EPIPE,就知道socket已经关闭了。
例如
int isSockStillConnected(int *fileDescriptors, int numFDs){
int i,n;
for (i=0;i<numFDs;i++){
n = write(fileDescriptors+i,"heartbeat",9);
if (n < 0) return -1;
if (errno == EPIPE) return -1;
}
//made it here, must be okay
return 0;
}

