如何使用C Berkeley socket检测TCP套接字的断开连接

10

我正在使用循环从一个C Berkeley套接字中读取消息,但是我无法检测到套接字何时被断开,因此我想要接受一个新的连接。请帮忙。

while(true) {
            bzero(buffer,256);
            n = read(newsockfd,buffer,255);
            printf("%s\n",buffer);        
}
3个回答

20

您能检测到套接字是否连接成功的唯一方式是向其写入数据。

read()/recv()时出现错误将指示连接已中断,但在读取时没有出现错误并不意味着连接已建立。

您可能会对阅读此内容感兴趣: http://lkml.indiana.edu/hypermail/linux/kernel/0106.1/1154.html

此外,使用TCP保持活动状态可以帮助区分非活动和断开的连接(即使应用程序没有要发送的数据,也会定期发送一些内容)。

(编辑:根据@Damon的指出,删除了错误的句子,谢谢。)


11
轻微修改:“读取0字节也可能仅意味着远端未有任何发送,不一定是问题。” -- 接收到0字节意味着另一端已经干净地关闭了连接。如果另一端没有任何要发送的数据,则在非阻塞套接字上会收到EAGAIN或EWOULDBLOCK错误,或者它将一直阻塞直到数据到达。检测连接失效的一种方法是在epoll上注册EPOLLHUP和EPOLLRDHUP事件。这并非100%可靠,但会报告有序关闭、半关闭和缺失保活的情况。 - Damon
2
@Pacerier,因为没有发送任何内容和链接损坏而没有收到任何内容之间没有区别。(实际上这是一个普遍原则。如果你早上没有收到任何信件,要么没有人给你发送任何东西,要么邮政系统不工作:你只能通过尝试通过邮寄发送一些东西来找出原因,或多或少。)这就是为什么在使用TCP连接时需要处理超时的原因。突然关闭的连接将不会向您发送任何内容告诉您它的关闭,因为它无法这样做。 - Bruno
1
@jean,确实可能会有延迟,但这是检测断开连接的唯一方法。如果您需要尽快知道,请刷新缓冲区。 (如果您正在等待读取错误,则可能要等很长时间...这不可靠。) - Bruno
1
@jean “如果是突然断开连接,使用write也无济于事,对吧?” 不,不是无济于事,它应该失败,因此您可以检测到断开连接。 您期望每15秒收到一个心跳的想法是不同的,可能会有更长的延迟而不关闭连接。 然后,您正在做出关于您认为可接受的延迟的任意决定(这通常是实际操作中正确的事情),但这并没有告诉您套接字是否已断开连接,这只是意味着您放弃并假设它已经断开连接。 - Bruno
1
@jean 不可以,这就是关键所在。请查看TCP流程图。如果你已经建立了连接,但是根本没有收到任何东西,那么你就不知道(a)电缆是否被切断(无论对方是否尝试通过发送FIN来关闭连接),还是(b)对方根本没有要发送的内容。如果连接已经建立,而且你不知道是否应该期望接收任何内容(你预先期望连接保持通畅),那么你不能仅凭阅读就知道。 - Bruno
显示剩余16条评论

0
你的问题在于完全忽略了read()返回的结果。在read()之后,你的代码至少应该像这样:
if (n == 0) // peer disconnected
    break;
else if (n == -1) // error
{
    perror("read");
    break;
}
else // received 'n' bytes
{
    printf("%.*s", n, buffer);
}

接受新连接应该在单独的线程中完成,而不是依赖于此连接的流结束。

bzero() 调用是无意义的,只是以前错误的解决方法。


-1

这是因为您没有使用keepalive超时。 在接收方,keepalive套接字选项是检测死连接的最佳解决方案。

但是,如果您的应用程序继续向套接字写入,则需要考虑更多问题。 即使您已将keepalive选项设置为应用程序套接字,在应用程序保持在套接字上写入的情况下,您无法及时检测套接字的死连接状态。 这是由于内核tcp堆栈的tcp重传机制造成的。 tcp_retries1和tcp_retries2是用于配置tcp重传超时的内核参数。 很难预测确切的重传超时时间,因为它是通过RTT机制计算的。 您可以在rfc793中看到此计算。(3.7. 数据通信)

https://www.rfc-editor.org/rfc/rfc793.txt

每个平台都有用于TCP重传的内核配置。

Linux : tcp_retries1, tcp_retries2 : (exist in /proc/sys/net/ipv4)

http://linux.die.net/man/7/tcp

HPUX : tcp_ip_notify_interval, tcp_ip_abort_interval

http://www.hpuxtips.es/?q=node/53

AIX : rto_low, rto_high, rto_length, rto_limit

http://www-903.ibm.com/kr/event/download/200804_324_swma/socket.pdf

如果您想要尽早检测到死连接,那么应该将tcp_retries2(默认为15)的值设置得更低,但正如我已经说过的那样,这不是精确的时间。

此外,目前您无法仅为单个套接字设置这些值。它们是全局内核参数。

曾经有一些尝试将tcp重传套接字选项应用于单个套接字(http://patchwork.ozlabs.org/patch/55236/),但我认为它没有被应用到内核主线中。我在系统头文件中找不到这些选项的定义。

作为参考,您可以通过'netstat --timers'来监视您的keepalive套接字选项,如下所示。 https://stackoverflow.com/questions/34914278

netstat -c --timer | grep "192.0.0.1:43245             192.0.68.1:49742"

tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (1.92/0/0)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (0.71/0/0)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (9.46/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (8.30/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (7.14/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (5.98/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (4.82/0/1)

此外,当保持连接超时发生时,您可能会遇到不同的返回事件,这取决于您使用的平台,因此您不能仅通过返回事件来确定死连接状态。 例如,HP返回POLLERR事件,而AIX在保持连接超时发生时只返回POLLIN事件。 此时,在recv()调用中会遇到ETIMEDOUT错误。
在最近的内核版本(自2.6.37以来),您可以使用TCP_USER_TIMEOUT选项。此选项可用于单个套接字。

不是这样的。这是因为他没有检查错误或者流结束。他完全忽略了reD()返回的结果。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接