【问题标题】:socket programming for bad network不良网络的套接字编程
【发布时间】:2014-01-23 07:24:20
【问题描述】:

客户:

socket(), connect() and then 
for (1 to 1024) { 
   write(1024 bytes)
}
exit(0);

服务器:

socket(), bind(), listen()
while (1) {
  accept()
  while((n = read()) {
     if (n == -1) abort(); /* never happended */
     total_read += n
  }
  close()
}

现在,客户端在 NAT 下的 Mac 上运行,服务器在我的 VPS(国外)上运行

一般来说,它工作正常(客户端发送所有数据并退出&服务器接收所有数据)

但是,当客户端正在运行但突然网络中断几分钟(并重新恢复)时,客户端不会在很长一段时间后退出......我用 control + C 杀死它并再次运行它,服务器似乎不再读取数据(客户端仍在运行)

这是 netstat 显示的内容:

客户:

tcp4       0 130312  192.168.1.254.58573    A.B.C.D.8888    ESTABLISHED

服务器:

tcp        0      0 A.B.C.D:8888     a.b.c.d:54566      ESTABLISHED 10970/a.out     
tcp   102136      0 A.B.C.D:8888     a.b.c.d:60916      ESTABLISHED - 

A.B.C.D 是我的 VPS 地址 a.b.c.d 是我的公共客户地址

我的问题是:

1,为什么?

2,服务器重启后就可以正常工作了,如何编写代码来摆脱它而不重启?

【问题讨论】:

    标签: sockets tcp network-programming


    【解决方案1】:

    在 TCP 中,除非您尝试在连接上发送某些内容,否则无法判断连接已失败。 TCP 不执行连接的主动监控(实际上,有可选的“keepalive”数据包,但这些通常在连接空闲几个小时后才会发送)。当你发送一些东西时,如果等待另一台机器返回确认超时,你最终会得到一个错误。但是,如果您只是读取数据而不发送,则无法判断连接已失败——看起来发送方没有任何要发送的内容。

    您可以通过设计应用程序来解决此问题,以便要求客户端每 N 秒发送一次内容。然后在服务器中设置一个计时器,检测到您在 N 秒内没有收到任何内容(您应该添加一点额外的时间以允许暂时的延迟)。

    【讨论】:

    • 谢谢,就像 FTP 中的 NOOP 我想从 netstat 了解有关 RecvQ 和 SendQ 的一些信息。服务器的 netstat 中有两项,第一项似乎是较新的连接,但第二项是旧的。可能是新连接的数据发送到旧连接,我猜...
    • 好答案。这是我不明白的。客户端套接字不应该由于进程退出而隐式关闭吗?因此关闭连接和服务器 read() 调用返回 0 ?其实没关系,健壮的服务器套接字代码应该针对各种可能性编写,并具有合理的超时值来处理死连接。
    • 客户端退出时,会向服务器发送一个FIN。如果网络已启动,服务器将收到此消息,读取 EOF 并退出循环。但如果网络出现故障,服务器将永远不会收到此消息。
    【解决方案2】:

    当网络中断时,您的客户端会继续发送数据,并且在某些时候套接字发送缓冲区已满(我从您的显示中了解到您正在发送 1024 字节,1024 次,总共 1MB)。发送缓冲区的默认值可能是 16KB(肯定小于 1MB)。然后当客户端尝试写入时,它会被永远阻塞。

    顺便说一句,现在我正在回答你的问题,我不知道最终在多次 TCP 超时之后,TCP 是否会放弃并关闭套接字,从而使套接字接口返回错误。我认为这不会发生...... :) - 所以,如果网络出现问题,连接会失败,但写入和读取不会失败。

    在服务器端,服务器在读取时被阻塞,因为它从未收到 EOF。

    解决方案:

    在客户端使用非阻塞套接字,如果网络中断,在某些时候写入将返回错误 EWOULDBLOCK。然后你会意识到由于某种原因发送缓冲区已满。此时,您可以关闭连接并尝试再次连接。如果网络中断,您将收到错误消息。

    在服务器端也使用非阻塞套接字和带有超时的 select() 函数。几次超时后,您可能会认为新连接有问题并关闭它。

    【讨论】:

    • write() 如果出现网络问题,确实会失败。发件人不会永远被阻止。 TCP 超时重试并重置连接,将 ECONNRESET 传递给发送者。默认发送缓冲区大小取决于平台。读取超时将解决 read() 问题。非阻塞 I/O 既不是必需的,也不是解决方案。太多的错误信息和猜测。 -1
    • @EJP,你是对的,客户端不会永远被阻止,只是很长一段时间。然后写入失败并出现主机无法访问错误 EHOSTUNREACH,如果您再次尝试写入,它将返回 EPIPE。我不知道写入可以接收 ECONNRESET,读取可以接收它以进行重置连接。当然,默认发送缓冲区大小取决于平台,但通常是千字节而不是 1MB。你是对的,读取超时将解决问题。非阻塞 I/O 对您来说可能不是必需的,但它肯定是解决它的一种方法。
    • EHOSTUNREACH 仅在 connect() 期间发生。 read() 可以接收 ECONNRESET,write() 也可以。非阻塞 I/O 不能解决这个问题。它只是防止您在 TCP 检测问题时阻塞。这仍然需要相同的时间。仅仅因为发送缓冲区已满而关闭连接是一个糟糕的建议。
    • @EJP,这是错误的。当发送的段无法到达目标节点时,EHOSTUNREACH 设置为套接字的未决错误。它可以在连接或写入时发生。除非收到设置了 RST 的段,否则您不会看到错误 ECONNRESET。您可以看到它在 Linux 内核中是如何工作的,例如在 TCP 使用的函数 ip_queue_xmit 中将一个段发送到 IP 层。我还可以为您提供 C 语言的客户端-服务器示例。如果您想更好地理解它,可以给我发送电子邮件至 rodolk@yahoo.com 或者我们可以打开另一个线程来讨论它。
    猜你喜欢
    • 2021-04-02
    • 2010-10-05
    • 2013-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-21
    • 2015-12-19
    • 2021-12-03
    相关资源
    最近更新 更多