题 确定对等体是否已关闭套接字的读取结束


我有一个套接字编程的情况,客户端关闭套接字的写入端,让服务器知道输入已完成(通过接收EOF),但保持读取端打开以读回结果(一行文本)。服务器知道客户端已成功读取结果并关闭套接字(或至少关闭读取端)将是有用的。有没有一种检查/等待这种状态的好方法?


12
2017-10-17 00:15


起源


EJP的回答提出了一个有效的问题 - 为什么服务器需要知道?这是一个严格的要求(例如,系统是否失败,或是发送响应的法律证据)还是软要求(监控成功的响应率,监控客户端速度)?请澄清你的问题。 - Dima Tisnek
这是一个软性要求。 - R..
@all,我欢迎展示解决方案的实用代码。这就是赏金的意义所在。 - Dima Tisnek
@qarma:唉,这几天的赏金似乎吸引了许多希望获得幸运的人的错误答案...... - R..


答案:


不可以。您可以知道的是,您的发送是否成功,并且由于TCP缓冲,其中一些即使在对等读取关闭后也会成功。

这是糟糕的设计。如果服务器需要知道客户端收到数据,则客户端需要确认它,这意味着它无法关闭其写入端。客户应该:

  1. 发送一个  终止消息,作为数据。
  2. 读取并确认所有进一步的响应,直到流结束。
  3. 关闭插座。

服务器应检测带内终止消息,并且:

  1. 停止从套接字读取请求
  2. 发送所有未完成的回复并阅读确认
  3. 关闭插座。

要么, 如果目标只是确保客户端和服务器同时结束,则每个端都应关闭其套接字以进行输出,然后读取输入直到流结束,然后关闭套接字。这样,最终关闭将在两端或多或少同时发生。


5
2017-10-17 00:52



你确定?肯定在客户端获得响应并关闭套接字后尝试发送更多数据最终会导致 ECONNRESET 或类似的,不是吗?似乎还有可能在没有实际尝试发送更多数据的情况下无法探测到这一点。 - R..
是, send() 会产生 ECONNRESET, 终于,经过一些成功的发送,因为缓冲,正如我所说。 - user207421
但是确认也可以通过关机顺序完成,不是吗?似乎简单而有效。 - usr
@usr我不知道怎么样。无论是否收到任何东西,客户端都会关闭或接通其插座。 - user207421
嗯,该应用程序只能优雅地关闭,以防它想要承认?应用程序崩溃应该导致重置,并且如果由于某种原因不希望应用程序可以重置应用程序。不过,这个方案对我来说听起来很可疑。听起来不好(但工作)。 @EJP - usr


getsockopt 同 TCP_INFO 似乎是最明显的选择,但它不是跨平台的。

这是Linux的一个例子:

import socket
import time
import struct
import pprint


def tcp_info(s):
    rv = dict(zip("""
            state ca_state retransmits probes backoff options snd_rcv_wscale
            rto ato snd_mss rcv_mss unacked sacked lost retrans fackets
            last_data_sent last_ack_sent last_data_recv last_ack_recv
            pmtu rcv_ssthresh rtt rttvar snd_ssthresh snd_cwnd advmss reordering
            rcv_rtt rcv_space
            total_retrans
            pacing_rate max_pacing_rate bytes_acked bytes_received segs_out segs_in
            notsent_bytes min_rtt data_segs_in data_segs_out""".split(),
                  struct.unpack("BBBBBBBIIIIIIIIIIIIIIIIIIIIIIIILLLLIIIIII",
                                s.getsockopt(socket.IPPROTO_TCP, socket.TCP_INFO, 160))))
    wscale = rv.pop("snd_rcv_wscale")

    # bit field layout is up to compiler
    # FIXME test the order of nibbles
    rv["snd_wscale"] = wscale >> 4
    rv["rcv_wscale"] = wscale & 0xf
    return rv

for i in range(100):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("localhost", 7878))
    s.recv(10)
    pprint.pprint(tcp_info(s))

我怀疑是否存在真正的跨平台替代方案。

从根本上说,有很多州:

  • 你把数据写入套接字,但它尚未发送
  • 数据已发送,但未收到
  • 数据已发送和丢失(依赖于计时器)
  • 已收到数据,但尚未确认
  • 尚未收到确认
  • 确认丢失(依赖于计时器)
  • 数据由远程主机接收,但未被应用程序读取
  • 应用程序读取数据,但socket仍然存活
  • 数据被读出,应用程序崩溃了
  • 数据被读出,应用程序关闭了套接字
  • 数据被读出,应用程序被调用 shutdown(WR) (几乎与关闭相同)
  • FIN尚未通过远程发送
  • FIN是通过远程发送但尚未收到的
  • FIN被送去并迷路了
  • FIN收到你的结尾

显然,你的操作系统可以区分这些状态中的很多,但不是全部。我想不出一个API就是这个冗长的......

某些系统允许您查询剩余的发送缓冲区空间。也许如果你这样做,并且套接字已经关闭,你会得到一个整洁的错误?

好消息是因为socket被关闭了,并不意味着你无法查询它。我可以得到所有的 TCP_INFO 关机后,用 state=7 (关闭)。在某些情况下报告 state=8 (等等)。

http://lxr.free-electrons.com/source/net/ipv4/tcp.c#L1961拥有Linux TCP状态机的所有血腥细节。


5
2017-10-19 09:57



在我的情况下,代码不需要是可移植的(它是针对特定的嵌入式应用程序)所以这可能是一个非常好的解决方案。谢谢! - R..


TL; DR:

不要依赖套接字状态;它可以在许多错误情况下削减你。您需要将确认/接收工具烘焙到您的通信协议中。用于status / ack的每一行的第一个字符对于基于文本的协议非常有效。


在许多(但不是全部)类Unix / POSIXy系统中,可以使用 TIOCOUTQ (也 SIOCOUTQIOCTL 确定传出缓冲区中剩余的数据量。

对于TCP套接字,即使另一端已关闭其写入端(因此不会向此发送更多数据),所有传输都会得到确认。仅当收到来自收件人内核的确认时,才会删除传出缓冲区中的数据。因此,当传出缓冲区中没有更多数据时,我们知道另一端的内核已经接收到数据。

不幸的是,这并不意味着 应用 已经收到并处理了数据。同样的限制适用于依赖套接字状态的所有方法;这也是从根本上确认收到/接受最终状态线的原因 必须来自其他应用程序,并且无法自动检测到。

反过来,这意味着在最终的接收/确认消息之前,任何一方都不能关闭其发送方。您不能依赖TCP - 或任何其他协议' - 自动套接字状态管理。您必须将关键收据/确认信息烘焙到流协议本身。

在OP的情况下,流协议似乎是简单的基于行的文本。这非常有用且易于解析。 “扩展”此类协议的一种可靠方法是为状态代码保留每行的第一个字符(或者,将某些单字符行保留为确认)。

对于大型飞行中的二进制协议(即发送方和接收方实际上并不真正同步的协议),用增加的(循环)整数标记每个数据帧是有用的,并且另一端有时会响应更新以让发件人知道哪些帧已被完全处理,哪些帧已收到,以及是否应该很快/很快就会到达其他帧。这对于消耗大量数据的基于网络的设备非常有用,数据提供者希望不断更新进度和所需的数据速率(想想3D打印机,CNC机器等,其中数据的内容动态更改最大可接受数据速率)。


1
2017-10-26 07:25





好的,所以我记得在90年代后期试图解决这个问题。我终于找到了一个不起眼的文档,声明对断开连接的套接字的读取调用将返回0.我今天使用这个事实。


-2
2017-10-22 04:23



你发现了 男人2 recv 要么 男人2读。这些或你发现的信息都没有什么模糊之处。 - user207421
这是针对相反方向的问题,这是微不足道的。 - R..
应该更仔细地阅读这个问题。抱歉。 - Bing Bang


你可能最好使用ZeroMQ。这将发送一个完整的消息,或根本没有消息。如果将其发送缓冲区长度设置为1(最短时间),则可以测试发送缓冲区是否已满。如果没有,可能是成功传输了消息。如果您的系统中存在不可靠或间歇性的网络连接,ZeroMQ也非常好。

这仍然不完全令人满意。你甚至可能更好地在ZeroMQ之上实现你自己的发送确认机制。这样你就可以绝对地证明收到了一条消息。您没有证据表明没有收到消息(发送和接收确认之间可能出错,而您无法解决两个常规问题)。但这是可以实现的最好的。你要做的就是在ZeroMQ的Actor模型之上实现一个Communicating Sequential Processes架构,它本身是在TCP流之上实现的。最终它有点慢,但是你的应用程序更确定知道发生了什么。


-3
2017-10-22 04:56



这个问题不是要求重新设计通信协议的建议,而是要求获得有关现有协议状态的更有用信息的方法。 - R..
@R ..适合自己。很多人,包括我自己,已经决定尽可能避免使用原装插座。如果您可以控制整个系统,并且可以自由选择自己的协议(从您的问题中并不完全清楚)并且之前没有使用过它,ZeroMQ或类似的东西(例如nanomsg)非常值得一看。 - bazza
@bazza并且有更多的人成功地实现了他们的要求,而无需部署第三方产品。 - user207421
@EJP,最终很多人通过利用免费,方便和极好的东西,更快地完成了他们的软件。这是一个选择的事情。 R ..似乎并不像我想的那样特别感兴趣,但那是他们的选择而不是我的选择。我所能做的只是指出有一个很好的选择。顺便提一下,套接字库不仅仅是ZeroMQ的第三方产品,即使它是像glibc这样构建的。哦,大多数网络应用程序没有直接使用源代码中的原始套接字;我确定你听说过一种叫做http的东西...... - bazza