欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

3次握手协议4次挥手协议和tcp状态跃迁

程序员文章站 2024-01-16 22:55:40
3次握手协议4次挥手协议和tcp状态跃迁   自己画的状态跃迁图,画的不漂亮,但是能说明问题,大家凑合着看吧,如果浏览时发现图片太小,看不清,可以放大网页。  ...

3次握手协议4次挥手协议和tcp状态跃迁

 

自己画的状态跃迁图,画的不漂亮,但是能说明问题,大家凑合着看吧,如果浏览时发现图片太小,看不清,可以放大网页。

 

3次握手协议4次挥手协议和tcp状态跃迁

 

我从3次握手协议和4次挥手协议来解释这个图,接下来提出并解决一些问题。

 

一、3次握手协议

 

1、在t1时刻,client端的进程要求和某个服务器进行连接。所以发送了一个同步请求帧,syn=1。从此刻开始client进入了syn sent阶段(t1-t3)。

 

2、在t2时刻,server端收到了来着client端的同步请求,并发送确认和同步请求,syn=1,ack=1,。从此刻开始server进入了syn receive阶段(t2-t4)。

 

3、在t3时刻,client收到了来着server端的同步和确认,client端马上发送ack=1,即确认帧,此时client由syn sent状态转到established(t3-t5)状态。

 

4、在t4时刻,server端收到了来着client端的确认,server由syn reveive状态转到established(t4-t6)状态。

 

到此3次握手协议结束,client和server可以互发数据。

 

二、4次挥手协议

 

5、在t5时刻,client发送撤销连接的请求,fin=1,此时client马上由established状态到FIN WAIT 1状态(t5-t7)。

 

6、在t6时刻,server收到client的撤销请求,并发送确认帧,这时server由established状态到closed wait状态(t6-t8)。

 

7、在t7时刻,client收到了来着server端的确认帧,client马上由FIN WAIT 1状态到FIN WAIT 2 状态(t7-t9)。

 

到此注意,server端并不马上发送撤销帧,因为server可能还有数据没传完。

 

8、在t8时刻,server端发送撤销连接的请求,并马上由closed wait状态转到last ack(t8-t10)状态,等待client发送确认帧。

 

9、在t9时刻,client收到了来着server端的撤销请求,并发送确认帧,这时,client端马上由FIN WAIT2状态到TIME WAIT(t9-t9+2msl)状态,并在经过2msl后,转到closed状态。

 

10、Server端在t10时刻收到了来着client端的确认帧,马上进入closed状态。 

 

到此四次挥手协议结束。

 

接下来是一些问题。(下面的一些问题和知识点均来着互联网)

 

1.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?

 

这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的连接请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可能未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。

 

2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?

 

这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。

 

3. 为什么不能用两次握手进行连接?

 

我们知道,3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

 

下面这个问题我还没找到答案,只是我的一些猜测

 

4、client发送撤销请求,server端确认后,表示一条连接已经断开,client将不能发送数据,但是可以接收数据,问题是,为什么接下来client收到server端的撤销请求后能发送确认帧,不是说只能接收数据不能发送数据吗?

 

答:连接和撤销请求均属于控制请求,处理数据的发送和接收的端口和处理连接、撤销服务的端口不一样,也就是说client端撤销请求后,client端不能发送数据给server端,但并不代表client端不能向server端发送请求连接和确认帧等。

 

补充:

 

a. 默认情况下(不改变socket选项),当你调用close( or closesocket,以下说close不再重复)时,如果发送缓冲中还有数据,TCP会继续把数据发送完。

 

b. 发送了FIN只是表示这端不能继续发送数据(应用层不能再调用send发送),但是还可以接收数据。

 

c. 应用层如何知道对端关闭?通常,在最简单的阻塞模型中,当你调用recv时,如果返回0,则表示对端关闭。在这个时候通常的做法就是也调用close,那么TCP层就发送FIN,继续完成四次握手。如果你不调用close,那么对端就会处于FIN_WAIT_2状态,而本端则会处于CLOSE_WAIT状态。这个可以写代码试试。

 

d. 在很多时候,TCP连接的断开都会由TCP层自动进行,例如你CTRL+C终止你的程序,TCP连接依然会正常关闭,你可以写代码试试。

 

 特别的TIME_WAIT状态:

 

从以上TCP连接关闭的状态转换图可以看出,主动关闭的一方在发送完对对方FIN报文的确认(ACK)报文后,会进入TIME_WAIT状态。TIME_WAIT状态也称为2MSL状态。

 

什么是2MSL?MSL即Maximum Segment Lifetime,也就是报文最大生存时间,引用《TCP/IP详解》中的话:“它(MSL)是任何报文段被丢弃前在网络内的最长时间。”那么,2MSL也就是这个时间的2倍。其实我觉得没必要把这个MSL的确切含义搞明白,你所需要明白的是,当TCP连接完成四个报文段的交换时,主动关闭的一方将继续等待一定时间(2-4分钟),即使两端的应用程序结束。你可以写代码试试,然后用setstat查看下。

 

为什么需要2MSL?根据《TCP/IP详解》和《The TCP/IP Guide》中的说法,有两个原因:

 

其一,保证发送的ACK会成功发送到对方,如何保证?我觉得可能是通过超时计时器发送。这个就很难用代码演示了。

 

其二,报文可能会被混淆,意思是说,其他时候的连接可能会被当作本次的连接。直接引用《The TCP/IP Guide》的说法:The second is to provide a “buffering period” between the end of this connection and any subsequent ones. If not for this period, it is possible that packets from different connections could be mixed, creating confusion.

 

TIME_WAIT状态所带来的影响:(1到4分钟)

 

当某个连接的一端处于TIME_WAIT状态时,该连接将不能再被使用。事实上,对于我们比较有现实意义的是,这个端口将不能再被使用。某个端口处于TIME_WAIT状态(其实应该是这个连接)时,这意味着这个TCP连接并没有断开(完全断开),那么,如果你bind这个端口,就会失败。对于服务器而言,如果服务器突然crash掉了,那么它将无法在2MSL内重新启动,因为bind会失败。解决这个问题的一个方法就是设置socket的SO_REUSEADDR选项。这个选项意味着你可以重用一个地址。

 

对于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状态。

 

断开连接时的意外:

 

这个算不上断开连接时的意外,当TCP连接发生一些物理上的意外情况时,例如网线断开,linux上的TCP实现会依然认为该连接有效,而windows则会在一定时间后返回错误信息。这似乎可以通过设置SO_KEEPALIVE选项来解决,不过不知道这个选项是否对于所有平台都有效。