TCP ------ TCP四次挥手(断开连接)及断开过程_数据

1、正常情况下,调用close(),产生的其中一个效果就是发送FIN,只有双方都调用close(),才会出现正常的四次挥手。

2、如果是服务器,发起四次挥手是在关闭accept()返回的套接字,而不是socket()返回的套接字

3、Initiator=client,Receiver=server 情况:如果是服务器进入CLOSE_WAIT,而不发送FIN的话(也就是不调用close()),重新创建服务器需要等待一段时间bind才能成功,这个时间就是客户端FIN_WAIT_2的超时时间,超时后客户端发送RST给服务器。所以客户端close(),服务器必须也执行close()

4、主动调用 close() 一方才会进入 TIME_WAIT

5、调用shutdown()并不会启动四次挥手

 

 

断开为什么需要四次握手:

TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。

如果客户端发起第一次挥手,服务端收到后就关闭连接,会导致服务端待发送给客户端的数据发不出去,导致数据丢失。

 

为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能到CLOSE状态

注:主动关闭一方会进入TIME_WAIT状态,以下假设Client是主动方

1、如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,保证能再次接收 FIN 并发送 ACK。(猜测: FIN超时时间应该小于MSL,否则Client等待2MSL,也可能接收不到FIN)

2、如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair,于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。

 

TIME_WAIT引发的问题:

服务端遇到的问题:客户端断网了,服务端检测到一定时间没有收到客户端数据执行close后,TIME_WAIT时间内不可以再次监听同一个端口号

客户端遇到的问题:由于客户端默认是使用随机端口和服务端通信,所以不会有问题,但是特殊情况,看 “对于TIME_WAIT的插曲” 章节

 

服务端为了解决这个TIME_WAIT问题,可选择的方式有三种:

    Ø  服务器关闭的时候使用RST的方式(这是网友说的,我使用ESP8266做服务器,发送RST《发送RST的原因是调用 close() 时接收缓冲区还有未读完的数据》还是会等待2MSL)

 Ø  规定close()的行为,服务器close()发送RST而不是FIN

    Ø  在创建socket,bind之前使能SO_REUSEADDR,然后close()(如果客户端断网了,假如服务器端没有keepalive(或者心跳包),服务器端没法知道客户端断网了,也就没法执行close()函数,而是傻傻继续等待客户端的数据)------- 适用于服务器不用检测客户端是否存在

    Ø  如果使用keepalive,客户端断开网络(比如客户端被拔了网线),服务器的keepalive机制可以检测到,read()返回失败,服务器再次创建时不需要等待2MSL ------- 适用于服务器需要检测客户端是否存在

 

 

 

对于TIME_WAIT的插曲:

   当建立一个TCP连接时,服务器端会继续用原有端口监听,同时用这个端口与客户端通信。而客户端默认情况下会使用一个随机端口与服务器端的监听端口通信。有时候,为了服务器端的安全性,我们需要对客户端进行验证,即限定某个IP某个特定端口的客户端。客户端可以使用bind来使用特定的端口。对于服务器端,当设置了SO_REUSEADDR选项时,它可以在2MSL内启动并listen成功。但是对于客户端,当使用bind并设置SO_REUSEADDR时,如果在2MSL内启动,虽然bind会成功,但是在windows平台上connect会失败。而在linux上则不存在这个问题。(我的实验平台:winxp, ubuntu7.10)

    要解决windows平台的这个问题,可以设置SO_LINGER选项。SO_LINGER选项决定调用close时TCP的行为。SO_LINGER涉及到linger结构体,如果设置结构体中l_onoff为非0,l_linger为0,那么调用close时TCP连接会立刻断开,TCP不会将发送缓冲中未发送的数据发送,而是立即发送一个RST报文给对方,这个时候TCP连接就不会进入TIME_WAIT状态。如你所见,这样做虽然解决了问题,但是并不安全。通过以上方式设置SO_LINGER状态,等同于设置SO_DONTLINGER状态。

 

 

套接字 SO_LINGER 选项可以规定 close() 的行为:

l_onoff0,则该选项关闭,l_linger的值被忽略,等于缺省情况,close立即返回;
l_onoff为非0l_linger0,则套接口关闭TCP连接时,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态; 
l_onoff 
为非0l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直 
a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成