前言
- 众所周知tcp传输层协议在建立连接的时候需要三次才能建立起一个真正的可靠连接,可是为什么是三次呢,不可以是两次,四次等等呢,可以自己思考一番,带着疑问可以看下文。
三次握手
在《计算机网络》一书中其中有提到,三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”,这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。
问题的本质是,信道是不可靠的,但是我们要建立可靠的连接发送可靠的数据,也就是数据传输是需要可靠的。在这个时候三次握手是一个理论上的最小值,并不是说是tcp协议要求的,而是为了满足在不可靠的信道上传输可靠的数据所要求的。
我们再来考虑,如果不是三次握手会出现什么情况呢:
假设有A和B两端要进行通信,
1, 第一次:首先A发送一个(SYN)到B,意思是A要和B建立连接进行通信;如果是只有一次握手的话,这样肯定是不行的,A压根都不知道B是不是收到了这个请求。
2, 第二次:B收到A要建立连接的请求之后,发送一个确认(SYN+ACK)给A,意思是收到A的消息了,B这里也是通的,表示可以建立连接;
如果只有两次通信的话,这时候B不确定A是否收到了确认消息,有可能这个确认消息由于某些原因丢了。
3, 第三次:A如果收到了B的确认消息之后,再发出一个确认(ACK)消息,意思是告诉B,这边是通的,然后A和B就可以建立连接相互通信了;
这个时候经过了三次握手,A和B双方确认了两边都是通的,可以相互通信了,已经可以建立一个可靠的连接,并且可以相互发送数据。
4, 第四次:这个时候已经不需要B再发送一个确认消息了,两边已经通过前三次建立了一个可靠的连接,如果再发送第四次确认消息的话,就浪费资源了。
如果第二个报文段B发出的(SYN+ACK)分别发送的话,也是可以理解为四次,但是被优化了,一起发送了。
- 超时重传机制,
(1) 如果第一个包,A发送给B请求建立连接的报文(SYN)如果丢掉了,A会周期性的超时重传,直到B发出确认(SYN+ACK);
(2) 如果第二个包,B发送给A的确认报文(SYN+ACK)如果丢掉了,B会周期性的超时重传,直到A发出确认(ACK);
(3) 如果第三个包,A发送给B的确认报文(ACK)如果丢掉了,
- A在发送完确认报文之后,单方面会进入ESTABLISHED的状态,B还是SYN_RCVD状态
- 如果此时双方都没有数据需要发送,B会周期性的超时发送(SYN+ACK),直到收到A的确认报文(ACK),此时B也进入ESTABLISHED状态,双方可以发送数据;
- 如果A有数据发送,A发送的是(ACK+DATA),B会在收到这个数据包的时候自动切换到ESTABLISHED状态,并接受数据(DATA);
- 如果这个时候B要发送数据,B是发送不了数据的,会周期性的超时重传(SYN+ACK)直到收到A的确认(ACK)B才能发送数据。
- 三次握手牵扯到的状态转换
- ** LISTEN ** 表示socket已经处于listen状态了,可以建立连接;
- ** SYN_SENT ** 表示socket在发出connect连接的时候,会首先发送SYN报文,然后等待另一端发送的确认报文(ACK),表示这端已经发送完SYN报文了;
- ** SYN_RCVD ** 表示一端已经接收到SYN报文了;
- ** ESTABLISHED ** 表示已经建立连接了,可以发送数据了。
四次挥手
- 说完TCP建立连接的时候为什么是三次,相对的就会想到为什么断开连接的时候是需要四次呢,而不是三次,五次等等呢;
- 本质的原因是tcp是全双公的,要实现可靠的连接关闭,A发出结束报文FIN,收到B确认后A知道自己没有数据需要发送了,B知道A不再发送数据了,自己也不会接收数据了,但是此时A还是可以接收数据,B也可以发送数据;当B发出FIN报文的时候此时两边才会真正的断开连接,读写分开。
- 四次挥手牵扯到的状态装换
- ** FIN_WAIT_1 ** 表示在等待另一方的FIN报文,和FIN_WAIT_2的区别是,FIN_WAIT_1表示socket现在要主动关闭连接,在发送完FIN报文后socket进入FIN_WAIT_1状态,当收到另一方发送FIN的ACK之后立即进入FIN_WAIT_2状态;
- ** FIN_WAIT_2 ** 同上,此时需要做的事情是可能还会接收数据,然后等待另一方的FIN;
- ** TIME_WAIT ** 存在主动关闭的一方,表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL(Max Segment Lifetime))后即可回到CLOSED可用状态了,需要等一段时间时原因是网络是不可靠的,不能保证这个ACK发送成功了,如果失败了,对端会超时重传FIN;
- ** CLOSING ** 表示在发送FIN之后,没有收到对方的ACK,而是收到了对方的FIN,这中情况很少见,只有在两端几乎同时关闭同一个socket的时候才会出现CLOSING状态;
- ** CLOSE_WAIT ** 表示收到对方的FIN之后,回给对方ACK,此时处于CLOSE_WAIT状态,等待关闭,要看自己是否还有数据要发送;
- ** LAST_ACK ** 表示收到对方的FIN之后,回给对方ACK,然后自己也要关闭发送FIN,等待另一方的ACK时候的状态;
- ** CLOSED ** 这个状态表示连接已经断开。
-
最后放一张状态转换图
后记
- 在状态转换图中省略了数据的发送;
- 对于tcp/ip的学习还需要努力,可能文中有些表达不够严谨,还望海涵。
*** 如有疑问欢迎批评指正,谢谢! ***