我一直在使用一个简单的服务器,它每30秒向一个客户端发送一个心跳包,然后通过心跳回复包确认心跳。当我通过发送SIGKILL,SIGSEGV来粗暴地终止服务器时,客户端通过select()和read()系统调用很容易发现它。然后我开始想知道当你在客户端写入心跳回复数据包之前这样做会发生什么,所以我在客户端代码中放置了20秒的睡眠并且同时杀死了服务器但发现客户端写入仍然成功。紧接着尝试第二次写入会触发预期的SIGPIPE信号并写入返回的EPIPE。据我所知,这是正常行为,但出于好奇,我打印出了客户端tcp状态。结果是:
所以我的问题是:
正如我现在所理解的那样:
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
答案 0 :(得分:3)
为什么第一次写入不会引发SIGPIPE并返回EPIPE?
TCP是异步的。您的只写将数据复制到套接字缓冲区并返回。 TCP堆栈在后台接管并用于发送该数据。换句话说,当send/sendmsg/write
返回时,并不意味着数据尚未发送。
当服务器被终止时,内核会为您在套接字上执行close
,发送未完成的数据,然后是FIN
,这会将您的客户端套接字置于TCP_CLOSE_WAIT
状态。它是一个半开连接状态,客户端仍然可以发送数据,只要服务器需要它。
您的客户端发送更多数据,但服务器操作系统以RST
响应,因为没有处理传入数据的进程。这会将您的客户端套接字放入TCP_CLOSE
。
我可以得出结论,如果第一次写入后TCP状态是TCP_CLOSE,那么与服务器的连接是否已关闭,或者我是否必须再次重新发送数据以确定?
TCP_CLOSE
是最终的TCP状态。不确定您的确切要求,但如果您需要确保其他对等方接收并处理了您的数据,则需要发回一些应用程序级别的消息。