【问题标题】:Sockets, TCP states and the write systemcall套接字、TCP 状态和写入系统调用
【发布时间】:2014-09-01 02:08:54
【问题描述】:

我一直在使用一个简单的服务器,它每 30 秒向客户端发送一个心跳包,然后客户端通过心跳回复包确认心跳。当我通过发送 SIGKILL、SIGSEGV 来残酷地终止服务器时,客户端很容易通过 select() 和 read() 系统调用发现这一点。然后我开始想知道当您在客户端写入其心跳回复数据包之前执行此操作时会发生什么,所以我在客户端代码中设置了 20 秒的睡眠时间并同时终止了服务器,但发现客户端写入仍然成功。之后立即尝试第二次写入会触发预期的 SIGPIPE 信号并写入返回的 EPIPE。然而,据我所知,这是正常行为,只是出于好奇,我打印了客户端 tcp 状态。结果是:

  1. TCP_ESTABLISHED - 在发送服务器 SIGKILL 之前。
  2. TCP_CLOSE_WAIT - 在第一次客户端写入之前的服务器端 SIGKILL 之后。
  3. TCP_CLOSE - 在第一次和第二次写入尝试之后。

所以我的问题是:

  1. 为什么第一次写入不引发 SIGPIPE 并返回 EPIPE?
  2. 如果在第一次写入后 TCP 状态为 TCP_CLOSE 是否可以断定与服务器的连接已关闭,或者我是否必须再重新发送一次数据才能确定?

目前我所理解的正在发生的事情的图表:

                       server                               client

          [ESTABLISHED]  |                                     | [ESTABLISHED] 
 SIGKILL or close () --> |                                     |  
          [FIN_WAIT_1]   |------------FIN M------------------->| [CLOSE_WAIT] 
                         |                                     |            ---\
          [FIN_WAIT_2]   |<-----------ACK M+1------------------|               |  
                         |                                     |               |   a read performed after a
          [TIME_WAIT]    |<-----------FIN N--------------------| [LAST_ACK?]   |-- serverside SIGKILL returns 0
                         |                                     |               |   but write succeeds
                         |------------ACK N+1----------------->| [CLOSE]       |
                         |                                     |            ---/
                         |                                     | 
                         |                                     |            ---\
                         |                                     | [CLOSE]       |   After the first write returns
                         |                                     |               |   the TCP/IP state is CLOSED 
                         |                                     | [CLOSE]       |   but even so only the a second 
                         |                                     |               |   returns EPIPE and raises SIGPIPE.
                         |                                     | [CLOSE]       |   
                         |                                     |               v 

【问题讨论】:

标签: linux sockets unix tcp


【解决方案1】:

为什么第一次写不引发 SIGPIPE 并返回 EPIPE?

TCP 是异步的。您的写入仅将数据复制到套接字缓冲区并返回。 TCP 堆栈在后台接管并发送该数据。也就是说,send/sendmsg/write 返回时,并不代表数据已经发送完毕。

当服务器被杀死时,内核会为您在套接字上执行close,发送未完成的数据,然后发送FIN,这会使您的客户端套接字进入TCP_CLOSE_WAIT 状态。这是一个半开的连接状态,客户端仍然可以发送数据,只要服务器期望它。

您的客户端发送更多数据,但服务器操作系统以RST 响应,因为没有处理传入数据的进程。这会将您的客户端套接字放入TCP_CLOSE

如果在第一次写入后 TCP 状态为 TCP_CLOSE 是否与服务器的连接已断开,我是否可以得出结论,或者我是否必须再重新发送一次数据才能确定?

TCP_CLOSE 是最终的 TCP 状态。不确定您到底在问什么,但如果您需要确保其他对等方接收并处理了您的数据,则需要发回一些应用程序级别的消息。

【讨论】:

  • 关于您的最后报价。如果我理解正确的话,在第一次写入之后,我不能确定服务器是否已关闭,直到我发送应用程序级数据(例如重复我的心跳回复数据包)以确保服务器确实已关闭?问题是,当第一次写入完成时,客户端已经知道套接字已关闭,因为它处于 TCP_CLOSE 状态,这让我有点困惑,它不只是在第一次写入后立即通过提高 SIGPIPE 来报告。当我发送更多数据时,客户端不会再次弹回 TCP_ESTABLISHED。
  • 在第一次写入后,我无法确定服务器是否已关闭,直到我发送应用级数据(例如重复我的心跳回复数据包)以确保服务器确实已关闭? - 当你收到一个 FIN 时,你是否仍然可以发送数据是不明确的,因为连接可以是半开或关闭的。第一次写入不会立即收到来自服务器的响应,RST 稍后会到达,您会在第二次写入时发现。
  • 所以基本上即使客户端在第一次写入后处于 TCP_CLOSE 状态,也不能保证服务器无法访问,我无法知道直到第二次写入之后的 RST 到达。因此,在写入时检查断开连接的正确方法是(1)执行写入,(2)测试 TCP_CLOSED,(3)如果状态为 TCP_CLOSED,再次写入数据以确保 RST 已到达并且连接正常真的关门了吗?
  • passive close scenario on the TCP state diagram。收到FIN 后,状态为CLOSE_WAIT,您仍然可以发送。在CLOSED 状态下,您无法发送。当您发送数据(第一次写入)并收到 RST 回复时,会出现状态 CLOSED
  • 如何从应用程序中检测 TCP 状态?通常,具有可靠传递的应用程序级协议在每条消息中都包含序列号,以便接收者可以检测消息间隙并请求重新传递或忽略重复消息。例如,请参阅 FIX 协议序列号。
猜你喜欢
  • 2013-05-25
  • 2014-01-31
  • 2010-12-25
  • 1970-01-01
  • 2022-01-25
  • 2021-08-17
  • 1970-01-01
  • 1970-01-01
  • 2013-01-03
相关资源
最近更新 更多