tcp要保证在所有可能的情况下使得所有的数据都能够被投递。当你关闭一个socket时,主动关闭一端的socket将进入time_wait状态,而被动关闭一方则转入closed状态,这的确能够保证所有的数据都被传输。当一个socket关闭的时候,是通过两端互发信息的四次握手过程完成的,当一端调用close()时,就说明本端没有数据再要发送了。这好似看来在握手完成以后,socket就都应该处于关闭closed状态了。但这有两个问题,首先,我们没有任何机制保证最后的一个ack能够正常传输,第二,网络上仍然有可能有残余的数据包(wandering duplicates),我们也必须能够正常处理。
通过正确的状态机,我们知道双方的关闭过程如下:
假设最后一个ack丢失了,服务器会重发它发送的最后一个fin,所以客户端必须维持一个状态信息,以便能够重发ack;如果不维持这种状态,客户端在接收到fin后将会响应一个rst,服务器端接收到rst后会认为这是一个错误。如果tcp协议能够正常完成必要的操作而终止双方的数据流传输,就必须完全正确的传输四次握手的四个节,不能有任何的丢失。这就是为什么socket在关闭后,仍然处于 time_wait状态,因为他要等待以便重发ack。
如果目前连接的通信双方都已经调用了close(),假定双方都到达closed状态,而没有time_wait状态时,就会出现如下的情况。现在有一个新的连接被建立起来,使用的ip地址与端口与先前的完全相同,后建立的连接又称作是原先连接的一个化身。还假定原先的连接中有数据报残存于网络之中,这样新的连接收到的数据报中有可能是先前连接的数据报。为了防止这一点,tcp不允许从处于time_wait状态的socket建立一个连接。处于time_wait状态的socket在等待两倍的msl时间以后(之所以是两倍的msl,是由于msl是一个数据报在网络中单向发出到认定丢失的时间,一个数据报有可能在发送图中或是其响应过程中成为残余数据报,确认一个数据报及其响应的丢弃的需要两倍的msl),将会转变为closed状态。这就意味着,一个成功建立的连接,必然使得先前网络中残余的数据报都丢失了。
由于time_wait状态所带来的相关问题,我们可以通过设置so_linger标志来避免socket进入time_wait状态,这可以通过发送rst而取代正常的tcp四次握手的终止方式。但这并不是一个很好的主意,time_wait对于我们来说往往是有利的。