您的位置:首页 > 理论基础 > 计算机网络

TCP三次握手/四次挥手 及 状态变迁图

2017-02-23 18:42 309 查看
转自:点击打开链接

【建立TCP连接】(三次握手)

   由于TCP协议提供可靠的连接服务,于是采用有保障的三次握手方式来创建一个TCP连接。三次握手的具体过程如下:

客户端发送一个带SYN标志的TCP报文(报文1)到服务器端,表示希望建立一个TCP连接。
服务器发送一个带ACK标志和SYN标志的TCP报文(报文2)给客户端,ACK用于对报文1的回应,SYN用于询问客户端是否准备好进行数据传输。
客户端发送一个带ACK标志的TCP报文(报文3),作为报文2的回应。

   至此,一个TCP连接就建立起来了。(详见下图)





【终止TCP连接】(四次挥手)

   由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。原则是主动关闭的一方(如已传输完所有数据等原因)发送一个FIN报文来表示终止这个方向的连接,收到一个FIN意味着这个方向不再有数据流动,但另一个方向仍能继续发送数据,直到另一个方向也发送FIN报文。四次挥手的具体过程如下:

客户端发送一个FIN报文(报文4)给服务器,表示我将关闭客户端到服务器端这个方向的连接。
服务器收到报文4后,发送一个ACK报文(报文5)给客户端,序号为报文4的序号加1。
服务器发送一个FIN报文(报文6)给客户端,表示自己也将关闭服务器端到客户端这个方向的连接。
客户端收到报文6后,发回一个ACK报文(报文7)给服务器,序号为报文6的序号加1。

   至此,一个TCP连接就关闭了。(4次挥手不是关闭TCP连接的唯一办法,见下文Q3疑问)



【TCP连接状态】

下面是每一个TCP连接在任意时刻可能处于的状态,在Linux下可以在netstat命令的最后一列(State列)里看到。

各个状态的含义如下:

CLOSED:初始状态,表示TCP连接是“关闭着的”或“未打开的”。
LISTEN :表示服务器端的某个SOCKET处于监听状态,可以接受客户端的连接。
SYN_RCVD :表示接收到了SYN报文。在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat很难看到这种状态,除非故意写一个监测程序,将三次TCP握手过程中最后一个ACK报文不予发送。当TCP连接处于此状态时,再收到客户端的ACK报文,它就会进入到ESTABLISHED
状态。
SYN_SENT :这个状态与SYN_RCVD 状态相呼应,当客户端SOCKET执行connect()进行连接时,它首先发送SYN报文,然后随即进入到SYN_SENT 状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT
状态表示客户端已发送SYN报文。
ESTABLISHED :表示TCP连接已经成功建立。
FIN_WAIT_1 :这个状态得好好解释一下,其实FIN_WAIT_1 和FIN_WAIT_2 两种状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET进入到FIN_WAIT_1
状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2 状态。当然在实际的正常情况下,无论对方处于任何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1 状态一般是比较难见到的,而FIN_WAIT_2 状态有时仍可以用netstat看到。
FIN_WAIT_2 :上面已经解释了这种状态的由来,实际上FIN_WAIT_2状态下的SOCKET表示半连接,即有一方调用close()主动要求关闭连接。注意:FIN_WAIT_2
是没有超时的(不像TIME_WAIT 状态),这种状态下如果对方不关闭(不配合完成4次挥手过程),那这个 FIN_WAIT_2 状态将一直保持到系统重启,越来越多的FIN_WAIT_2 状态会导致内核crash。
TIME_WAIT :表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP连接会等待2*MSL(Max
Segment Lifetime,最大分段生存期,指一个TCP报文在Internet上的最长生存时间。每个具体的TCP协议实现都必须选择一个确定的MSL值,RFC 1122建议是2分钟,但BSD传统实现采用了30秒,Linux可以cat /proc/sys/net/ipv4/tcp_fin_timeout看到本机的这个值),然后即可回到CLOSED 可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSING :这种状态在实际情况中应该很少见,属于一种比较罕见的例外状态。正常情况下,当一方发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING
状态表示一方发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?那就是当双方几乎在同时close()一个SOCKET的话,就出现了双方同时发送FIN报文的情况,这是就会出现CLOSING 状态,表示双方都正在关闭SOCKET连接。
CLOSE_WAIT :表示正在等待关闭。怎么理解呢?当对方close()一个SOCKET后发送FIN报文给自己,你的系统毫无疑问地将会回应一个ACK报文给对方,此时TCP连接则进入到CLOSE_WAIT状态。接下来呢,你需要检查自己是否还有数据要发送给对方,如果没有的话,那你也就可以close()这个SOCKET并发送FIN报文给对方,即关闭自己到对方这个方向的连接。有数据的话则看程序的策略,继续发送或丢弃。简单地说,当你处于CLOSE_WAIT
状态下,需要完成的事情是等待你去关闭连接。
LAST_ACK :当被动关闭的一方在发送FIN报文后,等待对方的ACK报文的时候,就处于LAST_ACK 状态。当收到对方的ACK报文后,也就可以进入到CLOSED 可用状态了。

【TCP状态变迁图】

   下面是收集自网上的几张图片,展示TCP连接的各种状态的变迁可能:



 





在进入CLOSING状态后,只要收到了对方对自己的FIN的ACK,就可以双双进入TIME_WAIT状态

 



 

 

【TCP相关疑问】

   最后整理几个常见的TCP相关的疑问:

Q1 为什么在TCP协议里,建立连接是三次握手,而关闭连接却是四次握手呢?
在谢希仁著《计算机网络》第四版中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。在另一部经典的《计算机网络》一书中讲“三次握手”的目的是为了解决“网络中存在延迟的重复分组”的问题。这两种不用的表述其实阐明的是同一个问题。谢希仁版《计算机网络》中的例子是这样的,“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”

A1 因为当处于LISTEN 状态的服务器端SOCKET当收到SYN报文(客户端希望新建一个TCP连接)后,它可以把ACK(应答作用)和SYN(同步作用)放在同一个报文里来发送给客户端。但在关闭TCP连接时,当收到对方的FIN报文时,对方仅仅表示对方没有数据发送给你了,但未必你的所有数据都已经全部发送给了对方,所以你大可不必马上关闭SOCKET(发送一个FIN报文),等你发送完剩余的数据给对方之后,再发送FIN报文给对方来表示你同意现在关闭连接了,所以通常情况下,这里的ACK报文和FIN报文都是分开发送的。
Q2为什么TIME_WAIT 状态还需要等2*MSL秒之后才能返回到CLOSED 状态呢?

1)可靠地实现TCP全双工连接的终止

TCP协议在关闭连接的四次握手过程中,最终的ACK是由主动关闭连接的一端(后面统称A端)发出的,如果这个ACK丢失,对方(后面统称B端)将重发出最终的FIN,因此A端必须维护状态信息(TIME_WAIT)允许它重发最终的ACK。如果A端不维持TIME_WAIT状态,而是处于CLOSED 状态,那么A端将响应RST分节,B端收到后将此分节解释成一个错误(在java中会抛出connection reset的SocketException)。

因而,要实现TCP全双工连接的正常终止,必须处理终止过程中四个分节任何一个分节的丢失情况,主动关闭连接的A端必须维持TIME_WAIT状态 。

注:setsockopt中的SO_REUSEADDR是让端口释放后立即就可以被再次使用

2)允许老的重复分节在网络中消逝 

TCP分节可能由于路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个迟到的迷途分节到达时可能会引起问题。在关闭“前一个连接”之后,马上又重新建立起一个相同的IP和端口之间的“新连接”,“前一个连接”的迷途重复分组在“前一个连接”终止后到达,而被“新连接”收到了。为了避免这个情况,TCP协议不允许处于TIME_WAIT状态的连接启动一个新的可用连接,因为TIME_WAIT状态持续2MSL,就可以保证当成功建立一个新TCP连接的时候,来自旧连接重复分组已经在网络中消逝。

Q3关闭TCP连接一定需要4次挥手吗?
A3不一定,4次挥手关闭TCP连接是最安全的做法。但在有些时候,我们不喜欢TIME_WAIT 状态(如当MSL数值设置过大导致服务器端有太多TIME_WAIT状态的TCP连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时我们可以通过设置SOCKET变量的SO_LINGER标志来避免SOCKET在close()之后进入TIME_WAIT状态,这时将通过发送RST强制终止TCP连接(取代正常的TCP四次握手的终止方式)。但这并不是一个很好的主意,TIME_WAIT
对于我们来说往往是有利的。
如果服务器出了异常,百分之八九十都是下面两种情况:

1.服务器保持了大量TIME_WAIT状态

2.服务器保持了大量CLOSE_WAIT状态

         因为linux分配给一个用户的文件句柄是有限的(可以参考:http://blog.csdn.net/shootyou/article/details/6579139),而TIME_WAIT和CLOSE_WAIT两种状态如果一直被保持,那么意味着对应数目的通道就一直被占着,而且是“占着茅坑不使劲”,一旦达到句柄数上限,新的请求就无法被处理了,接着就是大量Too Many Open Files异常,tomcat崩溃。
CLOSE_WAIT:如果我们的服务器一直处于CLOSE_WAIT状态的话,说明套接字是被动关闭的!,并且没有发送Fin信令,原因往往是没有调用TCP的CloseSocket

1、一般原因都是TCP连接没有调用关闭方法。需要应用来处理网络链接关闭。

2、对于Web请求出现这个原因,经常是因为Response的BodyStream没有调用Close.

解决方法:TCP的KeepLive功能,可以让操作系统替我们自动清理掉CLOSE_WAIT的连接。

但是KeepLive在Windows操作系统下默认是7200秒,也就是2个小时才清理一次。往往满足不了要求。可以调小该数值。

问题
      在TCP建立连接的三次握手阶段,如果客户端发送的第三个ACK包丢了,那么客户端和服务器端分别进行什么处理?

分析

     当Client端收到Server的SYN+ACK应答后,其状态变为ESTABLISHED,并发送ACK包给Server;

 如果此时ACK在网络中丢失,那么Server端该TCP连接的状态为SYN_RECV,并且依次等待3秒、6秒、12秒后重新发送SYN+ACK包(指数退避),以便Client重新发送ACK包    

 Server重发SYN+ACK包的次数,可以通过设置/proc/sys/net/ipv4/tcp_synack_retries修改,默认值为5。 如果重发指定次数后,仍然未收到ACK应答,那么一段时间后,Server自动关闭这个连接。但是Client认为这个连接已经建立,如果Client端向Server写数据,Server端将以RST包响应,方能感知到Server的错误。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: