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

时间:2016-10-17 00:15:34

标签: c sockets posix

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

5 个答案:

答案 0 :(得分:5)

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

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

  1. 发送带内终止消息,作为数据。
  2. 阅读并确认所有进一步的回复,直到流程结束。
  3. 关闭插座。
  4. 服务器应检测带内终止消息,并且:

    1. 停止从套接字读取请求
    2. 发送所有未完成的回复并阅读确认
    3. 关闭插座。
    4. OR,如果目标只是确保客户端和服务器同时结束,则每一端都应关闭其套接字以进行输出,然后读取输入直到流结束,然后关闭插座。这样,最终结束将在两端或多或少同时发生。

答案 1 :(得分:5)

带有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))

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

从根本上说,有很多州:

  • 您将数据写入套接字,但尚未发送
  • 已发送但未收到数据
  • 数据已发送并丢失(依赖于计时器)
  • 已收到数据,但尚未确认
  • 尚未收到确认
  • 确认丢失(依赖于计时器)
  • 数据由远程主机接收但未由应用程序读取
  • 数据已被应用程序读出,但套接字仍处于活动状态
  • 数据被读出,应用程序崩溃
  • 数据被读出,app关闭了套接字
  • 数据被读出,应用程序名为shutdown(WR)(几乎与关闭相同)
  • FIN尚未远程发送
  • FIN是远程发送但尚未收到
  • FIN被送去并迷路了
  • FIN收到你的结尾

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

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

好消息是因为套接字被关闭,并不意味着你无法查询它。我可以在关机后使用TCP_INFO(已关闭)获取所有state=7。在某些情况下报告state=8(等待等待)。

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

答案 2 :(得分:1)

TL; DR:

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

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

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

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

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

在OP的情况下,流协议似乎是简单的基于行的文本。这非常有用且易于解析。一种强有力的方式来扩展"这样的协议是为状态代码保留每一行的第一个字符(或者,保留某些单字符行作为确认)。

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

答案 3 :(得分:-2)

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

答案 4 :(得分:-3)

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

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