APP下载

TCP为什么需要三次握手?两次可以吗?TIME_WAIT状态有什么用?

消息来源:baojiabao.com 作者: 发布时间:2024-05-06

报价宝综合消息TCP为什么需要三次握手?两次可以吗?TIME_WAIT状态有什么用?

先说下三次握手的过程

多么清晰的一张图,当然了,也不是我画的,我也只是引用过来说明问题了。

第一次握手:建立连线。客户端传送连线请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设定Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要传送SYN请求资讯,将SYN位置为1,Sequence Number为y;服务器端将上述所有资讯放到一个报文段(即SYN+ACK报文段)中,一并传送给客户端,此时服务器进入SYN_RECV状态;第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设定为y+1,向服务器传送ACK报文段,这个报文段传送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。 完成了三次握手,客户端和服务器端就可以开始传送资料。以上就是TCP三次握手的总体介绍。那四次分手呢?

当客户端和服务器通过三次握手建立了TCP连线以后,当资料传送完毕,肯定是要断开TCP连线的啊。那对于TCP的断开连线,这里就有了神秘的“四次分手”。

第一次分手:主机1(可以使客户端,也可以是服务器端),设定Sequence Number和Acknowledgment Number,向主机2传送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有资料要传送给主机2了;第二次分手:主机2收到了主机1传送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求;第三次分手:主机2向主机1传送FIN报文段,请求关闭连线,同时主机2进入LAST_ACK状态;第四次分手:主机1收到主机2传送的FIN报文段,向主机2传送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连线;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连线了。为什么要三次握手

在谢希仁著《计算机网络》第四版中讲“三次握手”的目的是“为了防止已失效的连线请求报文段突然又传送到了服务端,因而产生错误”。在另一部经典的《计算机网络》一书中讲“三次握手”的目的是为了解决“网络中存在延迟的重复分组”的问题。

在谢希仁著《计算机网络》书中同时举了一个例子,如下:

“已失效的连线请求报文段”的产生在这样一种情况下:client发出的第一个连线请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连线释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连线请求报文段后,就误认为是client再次发出的一个新的连线请求。于是就向client发出确认报文段,同意建立连线。假设不采用“三次握手”,那么只要server发出确认,新的连线就建立了。由于现在client并没有发出建立连线的请求,因此不会理睬server的确认,也不会向server传送资料。但server却以为新的运输连线已经建立,并一直等待client发来资料。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连线。

这就很明白了,防止了服务器端的一直等待而浪费资源。

为什么要四次分手

那四次分手又是为何呢?TCP协议是一种面向连线的、可靠的、基于字节流的运输层通讯协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有资料要传送了,主机1告诉主机2,它的资料已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的资料;当主机2返回ACK报文段时,表示它已经知道主机1没有资料传送了,但是主机2还是可以传送资料到主机1的;当主机2也传送了FIN报文段时,这个时候就表示主机2也没有资料要传送了,就会告诉主机1,我也没有资料要传送了,之后彼此就会愉快的中断这次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连线,但另外还告诉对方,我暂时还有点资料需要传送给你(ACK资讯),稍后再关闭连线。(主动方)

CLOSE_WAIT:这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后传送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有资料传送给对方,如果没有的话,那么你也就可以 close这个SOCKET,传送FIN报文给对方,也即关闭连线。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连线。(被动方)

LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在传送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)

TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FINWAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)

CLOSED: 表示连线中断。

从TCP状态迁移图可知,只有首先呼叫close()发起主动关闭的一方才会进入TIME_WAIT状态,而且是必须进入(图中左下角所示的3条状态迁移线最终均要进入该状态才能回到初始的CLOSED状态)。

从图中还可看到,进入TIME_WAIT状态的TCP连线需要经过2MSL才能回到初始状态,其中,MSL是指Max

Segment Lifetime,即资料包在网络中的最大生存时间。每种TCP协议的实现方法均要指定一个合适的MSL值,如RFC1122给出的建议值为2分钟,又如Berkeley体系的TCP实现通常选择30秒作为MSL值。这意味着TIME_WAIT的典型持续时间为1-4分钟。

TIME_WAIT状态存在的原因主要有两点:

1)为实现TCP这种全双工(full-duplex)连线的可靠释放

参考本文前面给出的TCP释放连线4次挥手示意图,假设发起active close的一方(图中为client)传送的ACK(4次互动的最后一个包)在网络中丢失,那么由于TCP的重传机制,执行passiveclose的一方(图中为server)需要重发其FIN,在该FIN到达client(client是active close发起方)之前,client必须维护这条连线的状态(尽管它已呼叫过close),具体而言,就是这条TCP连线对应的(local_ip, local_port)资源不能被立即释放或重新分配。直到romete peer重发的FIN达到,client也重发ACK后,该TCP连线才能恢复初始的CLOSED状态。如果activeclose方不进入TIME_WAIT以维护其连线状态,则当passive close方重发的FIN达到时,active close方的TCP传输层会以RST包响应对方,这会被对方认为有错误发生(而事实上,这是正常的关闭连线过程,并非异常)。

2)为使旧的资料包在网络因过期而消失

为说明这个问题,我们先假设TCP协议中不存在TIME_WAIT状态的限制,再假设当前有一条TCP连线:(local_ip, local_port, remote_ip,remote_port),因某些原因,我们先关闭,接着很快以相同的四元组建立一条新连线。本文前面介绍过,TCP连线由四元组唯一标识,因此,在我们假设的情况中,TCP协议栈是无法区分前后两条TCP连线的不同的,在它看来,这根本就是同一条连线,中间先释放再建立的过程对其来说是“感知”不到的。这样就可能发生这样的情况:前一条TCP连线由local peer传送的资料到达remote peer后,会被该remot peer的TCP传输层当做当前TCP连线的正常资料接收并向上传递至应用层(而事实上,在我们假设的场景下,这些旧资料到达remote peer前,旧连线已断开且一条由相同四元组构成的新TCP连线已建立,因此,这些旧资料是不应该被向上传递至应用层的),从而引起资料错乱进而导致各种无法预知的诡异现象。作为一种可靠的传输协议,TCP必须在协议层面考虑并避免这种情况的发生,这正是TIME_WAIT状态存在的第2个原因。

具体而言,local peer主动呼叫close后,此时的TCP连线进入TIME_WAIT状态,处于该状态下的TCP连线不能立即以同样的四元组建立新连线,即发起active close的那方占用的local port在TIME_WAIT期间不能再被重新分配。由于TIME_WAIT状态持续时间为2MSL,这样保证了旧TCP连线双工链路中的旧资料包均因过期(超过MSL)而消失,此后,就可以用相同的四元组建立一条新连线而不会发生前后两次连线资料错乱的情况。

另一比较深入的说法

TIME_WAIT状态的存在有两个理由:(1)让4次握手关闭流程更加可靠;4次握手的最后一个ACK是是由主动关闭方传送出去的,若这个ACK丢失,被动关闭方会再次发一个FIN过来。若主动关闭方能够保持一个2MSL的TIME_WAIT状态,则有更大的机会让丢失的ACK被再次传送出去。(2)防止lost duplicate对后续新建正常连结的传输造成破坏。lost duplicate在实际的网络中非常常见,经常是由于路由器产生故障,路径无法收敛,导致一个packet在路由器A,B,C之间做类似死循环的跳转。IP头部有个TTL,限制了一个包在网络中的最大跳数,因此这个包有两种命运,要么最后TTL变为0,在网络中消失;要么TTL在变为0之前路由器路径收敛,它凭借剩余的TTL跳数终于到达目的地。但非常可惜的是TCP通过超时重传机制在早些时候传送了一个跟它一模一样的包,并先于它达到了目的地,因此它的命运也就注定被TCP协议栈抛弃。另外一个概念叫做incarnation connection,指跟上次的socket pair一摸一样的新连线,叫做incarnation of previous connection。lost duplicate加上incarnation connection,则会对我们的传输造成致命的错误。大家都知道TCP是流式的,所有包到达的顺序是不一致的,依靠序列号由TCP协议栈做顺序的拼接;假设一个incarnation connection这时收到的seq=1000, 来了一个lost duplicate为seq=1000, len=1000, 则tcp认为这个lost duplicate合法,并存放入了receive buffer,导致传输出现错误。通过一个2MSL TIME_WAIT状态,确保所有的lost duplicate都会消失掉,避免对新连线造成错误。

Q: 编写 TCP/SOCK_STREAM 服务程式时,SO_REUSEADDR到底什么意思?

A: 这个套接字选项通知核心,如果埠忙,但TCP状态位于 TIME_WAIT ,可以重用

埠。如果埠忙,而TCP状态位于其他状态,重用埠时依旧得到一个错误资讯,

指明"地址已经使用中"。如果你的服务程式停止后想立即重启,而新套接字依旧

使用同一埠,此时 SO_REUSEADDR 选项非常有用。必须意识到,此时任何非期

望资料到达,都可能导致服务程式反应混乱,不过这只是一种可能,事实上很不

可能。

2020-01-23 07:58:00

相关文章