有没有办法检测远程对等体已关闭TCP套接字,而不读取它?

Jer*_*ner 16 c select tcp

首先,一些背景来解释动机:我正在研究一个非常简单的基于select()的TCP"镜像代理",它允许两个防火墙的客户端间接地相互通信.两个客户端都连接到此服务器,并且只要两个客户端都连接,客户端A发送到服务器的任何TCP字节都将转发到客户端B,反之亦然.

这或多或少都有效,只有一个小问题:如果客户端A连接到服务器并在客户端B连接之前开始发送数据,则服务器没有任何地方可以放置数据.我不想在RAM中缓冲它,因为这可能最终使用大量的RAM; 我也不想丢弃数据,因为客户端B可能需要它.所以我选择了第三个选项,即在客户端B连接之前,不要在客户端A的套接字上选择() - for-read-ready.这样客户端A就会阻塞,直到一切准备就绪.

这或多或少也有效,但是在客户端A的套接字上没有选择准备就绪的副作用是,如果客户端A决定关闭他与服务器的TCP连接,则服务器不会收到有关该事实的通知 - - 至少,直到客户端B出现并且服务器最终在客户端A的套接字上选择for-ready-ready,读取任何未决数据,然后获得套接字关闭通知(即recv()返回0).

如果服务器有某种方式知道(及时)客户端A关闭他的TCP连接,我更喜欢它.有没有办法知道这个?在这种情况下轮询是可以接受的(例如,我可以让select()每分钟唤醒一次并在所有套接字上调用IsSocketStillConnected(sock),如果存在这样的函数).

jxh*_*jxh 9

如果要检查套接字是否实际关闭而不是数据,可以添加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 */
    }
}
Run Code Online (Sandbox Code Playgroud)

即使A在发送一些数据后关闭,您的代理可能希望在将FIN转发给B之前先将该数据转发给B,因此没有必要知道A已经在连接上发送了FIN,而不是在读取所有数据之后它发送了.

在双方发送FIN之前,TCP连接不被视为关闭.但是,如果A强行关闭其端点,那么在您尝试发送数据并接收到EPIPE(假设您已被抑制SIGPIPE)之后,您才会知道这一点.


在阅读了您的镜像代理应用程序之后,由于这是一个防火墙遍历应用程序,您似乎确实需要一个小的控制协议来允许您验证这些对等实际上是否允许彼此通信.如果您有控制协议,那么您可以使用许多解决方案,但我提倡的那个解决方案是将其中一个连接描述为服务器,另一个连接将自身描述为客户端.然后,如果没有服务器,则可以重置客户端的连接以进行连接.您可以让服务器等待客户端连接达到某个超时.服务器不应启动任何数据,如果没有连接的客户端,则可以重置服务器连接.这消除了为死连接缓冲数据的问题.


Jer*_*ner 2

我的问题的答案似乎是“不,除非您愿意并且能够修改 TCP 堆栈以访问必要的私有套接字状态信息”。

由于我无法做到这一点,我的解决方案是重新设计代理服务器,以始终读取来自所有客户端的数据,并丢弃来自合作伙伴尚未连接的客户端的任何数据。这不是最佳的,因为这意味着通过代理的 TCP 流不再具有使用 TCP 的程序所期望的可靠按顺序传递的类似流的属性,但它足以满足我的目的。