客户端无法预测的中断关闭

时间:2015-04-30 09:11:33

标签: c sockets winapi tcp-ip winsock2

我正在使用相当基本的服务器/客户端设置,两者都位于同一网络上。它们通过TCP / IP通过Winsock2 阻塞套接字进行通信,并且完全正常。

但是,对于下面描述的方案,客户端有时会看到中止连接终止(RST)。它大约在100次中的99次,但最后一次令人烦恼地失败了一些测试,因此,我的整个构建。它在何时何地发生是完全不可预测的,因此复制问题迄今为止还没有找到。

如果我正确理解the relevant MSDN page阻止套接字的名义连接终止序列可以概括为:

Client               |  Server
-----------------------------
shutdown(SD_SEND)    |  
                     |  send() response data
i=recv() until i==0  |  shutdown(SD_SEND) 
closesocket()        |  closesocket() 

在我的设置中,有必要

  1. 做一个相对昂贵的操作(让它称之为expensive_operation())取决于接收数据的一部分(比如512字节)是否包含触发值。服务器是单线程的,因此expensive_operation()有效地阻止recv()数据流直到expensive_operation()完成
  2. 如果客户端发送特定的标记值,则
  3. 启动服务器关闭序列,让我们将其称为0xDEADBEEF
  4. 我的客户端已实施,以便始终发送最后的sentinel值,因此在发送后,不会发送其他数据:

    1. send( "data data data 0xDEADBEEF" )到服务器
    2. shutdown(SD_SEND) < -------失败发生在这里
    3. recv()直到收到0字节
    4. closesocket()
    5. 每当服务器收到0xDEADBEEF时,它都会确认关机请求并继续终止:

      1. recv() 512字节数据或返回0字节
      2. 检查触发器。如果找到触发器,请执行expensive_operation()并返回步骤1,否则继续
      3. 检查哨兵值。如果未找到sentinel,请返回步骤1.
      4. 如果发现哨兵:
        1. send( confirmation )到客户
        2. shutdown(SD_SEND)
        3. closesocket()
        4. 所有正常服务器关闭的东西
      5. 我可以理解,如果客户端打算在标记之后发送更多数据,这将导致中断连接终止 - 因为服务器主动终止连接。这完全是预期的,并且在设计上,我确实可以可靠地重现这种行为。

        然而,在名义上的情况下,序列中的前哨总是最后,确实始终发生,如相关日志条目所证明的那样,确实是优雅的连接终止大部分时间都按预期发生。但并不总是 ......

        正如我所说,它是随机发生的,所以我无法生成可靠地重现问题的代码片段。唯一一致的是,在客户端调用shutdown()时总是会发生失败......

        我怀疑它更多的是设计缺陷,或者我尚未处理的某些同步问题,而不是代码问题(尽管我很乐意提供相关代码片段)。

        有什么明显的东西我可以俯瞰这里吗?

2 个答案:

答案 0 :(得分:1)

有几种方法可以激发RST在发送端故意这样做,我不会在这里透露:

  1. 写入已被对等方关闭的连接。尝试几次后,这将导致ECONNRESET.
  2. 关闭连接,但未读取所有已挂起的数据。这将导致立即ECONNRESET.
  3. 这两个都表明应用程序协议错误。

    在你的情况下,我会摆脱哨兵。这是多余的。只需关闭套接字以进行输出,或者只是在知道没有更多数据进入时关闭套接字。这会向对等方发送一个完全明确的指示,即没有更多数据,而不要求对等方精确地同步字节为与本地应用程序的字节,这是当前代码中此错误的弱点和可能来源。

    您需要发布一些代码才能获得更具体的帮助。

答案 1 :(得分:0)

我无法重现,但我可以想象客户端看到无效终止的用例

client           server
send sentinel
                 send confirmation
                 shutdown
                 close socket
shutdown                               => error writing on closedsocket !

如果客户端进程在发送其sentinel之后被抢占,并且如果服务器很快,则可以落入该场景。这是因为服务器端在关机后立即关闭套接字而不确定客户端是否已完成关闭。恕我直言,你应该做

send( confirmation ) to client
shutdown(SD_SEND)
read until 0 or error
closesocket()
all the normal server shutdown stuff

然后,上部的顺序是确定性的:

client           server
send sentinel
                 send confirmation
shutdown         shutdown
                 recv 0
                 close socket     => cannot happen before client shutdown
recv 0                            => socket may be closed server side but it would be harmless