【问题标题】:Python doesn't detect a closed socket until the second sendPython 在第二次发送之前不会检测到关闭的套接字
【发布时间】:2011-02-05 02:43:58
【问题描述】:

当我关闭连接一端的套接字时,另一端第二次发送数据时出错,但不是第一次:

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("localhost", 12345))
server.listen(1)

client = socket.create_connection(("localhost",12345))
sock, addr = server.accept()
sock.close()

client.sendall("Hello World!")    # no error
client.sendall("Goodbye World!")  # error happens here

我尝试设置 TCP_NODELAY,使用 send 而不是 sendall,检查 fileno(),我找不到任何方法让第一次发送引发错误,甚至无法在事后检测到它失败. 编辑:sock.close 之前调用sock.shutdown 没有帮助。 编辑#2:即使在关闭之后和写入之前添加time.sleep 也没有关系。 编辑#3:检查send 返回的字节数没有帮助,因为它总是返回消息中的字节数。

因此,如果我想检测错误,我能想出的唯一解决方案是在每个 sendall 后面加上 client.sendall(""),这会引发错误。但这似乎很骇人听闻。我使用的是 Linux 2.6.x,因此即使解决方案仅适用于该操作系统,我也会很高兴。

【问题讨论】:

  • 我遇到了同样的问题。您最终是如何解决代码中的问题的?
  • @HighCommander:我最终在每个 send 之后添加了一个 client.sendall(""),这在技术上不能保证会出错,但适用于我的程序,给出了我正在做的时间和细节.这是不优雅的,我真的希望有一种方法可以访问底层 TCP 套接字状态,但可惜在 Python 或 C 中似乎没有。
  • 当客户端和服务器在不同的机器上时,这对你有用吗?它不适合我,在这种情况下 sendall() 会成功。
  • @HighCommander:这对我在讨论localhost 时有用,但我没有在多台机器上尝试过,实际上我不希望它在这些情况下是可靠的,因为它会需要更长的时间来检测故障。如果您在本地 Intranet 上运行,并且往返时间相当一致,并且性能不是一个大问题,那么您可以在第二次发送之前添加一个短暂的睡眠。

标签: python sockets


【解决方案1】:

这是意料之中的,以及 TCP/IP API 的实现方式(因此在几乎所有语言和所有操作系统中都是相似的)

简而言之,如果 send() 调用以某种方式无法将数据传递到另一端,则您无法确保 send() 调用直接返回错误。 send/write 调用只是将数据传送到 TCP 堆栈,并由 TCP 堆栈在可能的情况下传送。

TCP 也只是一个传输协议,如果您需要知道您的应用程序“消息”是否已到达另一端,您需要自己实现(某种形式的 ACK),作为应用程序协议的一部分 - 没有其他免费午餐。

然而——如果你从一个套接字中读取(),当发生错误或另一端关闭套接字时,你可以立即得到通知——你通常需要以某种形式的多路复用事件循环来执行此操作(即,使用 select/poll 或其他一些 IO 多路复用工具)。

请注意,您无法从套接字中读取()来了解最近的发送/写入是否成功,这里有几个案例说明原因(但这是人们没有想到的案例)

  • 由于网络拥塞,或者因为 tcp 窗口关闭(可能是读取器速度较慢),然后另一端关闭了套接字或发生了硬网络错误,所以几次 write() 调用被缓冲,因此您无法判断如果是最后一次未通过的写入,或者是您在 30 秒前完成的写入。
  • 网络错误,或防火墙静默丢弃您的数据包(不生成 ICMP 回复),您必须等到 TCP 连接超时才能收到错误,该错误可能会持续数秒,通常是几分钟。
  • 当您调用send 时,TCP 正忙于重新传输 - 可能这些重新传输会产生错误。(实际上与第一种情况相同)

【讨论】:

  • 感谢您的详细回答,但我对此仍然有些困惑。连接的另一端不是发送 ACK 数据包来确认它收到了数据吗?我从您的回答中假设send 不会等待此确认,但是在我们知道我们的数据通过之前,是否有任何其他方法可以阻止(在传输级别)?我知道通常这应该发生在应用程序级别,但我目前正在谈论一个没有这种确认的协议,所以我想知道我在传输级别的选择是什么。
  • 还有,TCP在关闭的时候不发送FIN包吗?
  • @Eli Courtwright。是的。 TCP 发送 ACK。然而,那是 TCP(传输层)的 ACK。 TCP 不知道您的消息,就 TCP 而言,它只是一个字节流。 TCP ACK 不会确认一次 send() 调用。它可以确认 100 条 your 消息的累积。或 3 个字节的消息。
  • 否 - 没有等待 ACK 的好方法。是的 - 当套接字关闭时发送 FIN(或 RST,取决于状态)。想象一下如果 FIN 数据包消失会发生什么(路由器/交换机丢弃数据包比大多数人想象的更常见)。另外 - 你怎么知道在你调用 send() 时收到了 FIN 数据包?也许它在您调用 send() 后 3 纳秒收到。如果您还从该套接字读取(),您可以检测到该 close() ,并且您也会收到发送错误的通知 - 但仅限于完美的情况。但是,没有来自读取(或选择/轮询)的关闭/错误“事件”是非决定性的。
  • 感谢您的详细信息;我已将您的答案标记为已接受,尽管为此我讨厌 TCP 堆栈(或可能是 Python 套接字库)。我知道一般收据验证应该在应用程序级别,而不是 TCP 级别。但是,虽然看到 TCP 数据包到达并不足以证明数据到达了它要去的地方(例如,应用程序可能在之后保存之前崩溃了),但看到 TCP 数据包实际上没有到达就足以知道数据没有到达哪里它正在运行。所以作为一名程序员,我想访问这个失败数据。
【解决方案2】:

根据docs,在调用sock.close()之前尝试调用sock.shutdown()

【讨论】:

  • 赞成建议我没有尝试过的东西(他们没有在 socket.close 文档字符串中提到关闭),但不幸的是这也没有效果。
  • 在这种情况下, shutdown() 不会超过 close() 。而且即使是这样 - 如果有轻微的网络延迟,另一端不会立即收到关闭请求,第一次 send() 调用仍然会成功,你又会遇到同样的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-13
  • 1970-01-01
  • 2012-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多