TCP报文的分类
TCP看似复杂,其实可以归纳为以下5种报文:
- SYN(建立连接报文)
- Data (携带用户数据的数据传输报文)
- FIN(断开连接报文)
- Reset(重置连接报文)
- ACK(ACK确认报文)
TCP报文的确认问题
其中1、2、3分别为建立连接、数据传输、断开连接,这三种报文接收方在接收到后,一定要向发送方发送ACK确认报文以表示确认。
为何要确认,因为这就是可靠传输依赖的机制。如果接收方在超时时间内不确认,发送方就会一直重传,直到接收方确认为止,或者到达了重传上限次数,最终Reset连接。
4、5 为重置连接报文、确认ACK报文,这两种报文接收方接收到后要发送ACK确认报文以确认吗?不需要!因此,自然地,发送方也不会重传这2种类型的报文(因为由于没有确认,对于发送方而言,它根本不知道数据报有没有到达接收方,因而重发机制无从设置)。
为何Reset报文不需要ACK确认?
因为发送Reset报文的一端,在发送完这个报文之后,和该TCP Session有关的内存结构体瞬间全部释放,无论接收方收到或没有收到,关系并不大。
如果接收方收到Reset报文,也会释放该TCP Session 的相关内存结构体。
如果接收方没有收到Reset 报文,可能会继续发送让接收方弹射出Reset报文的报文,到最后对方一样会收到Reset 报文,并最终释放内存。
为何ACK报文不需要ACK确认?
这里的ACK报文,是指没有携带任何数据的裸ACK报文,对方收到这样的ACK报文,自然也不需要ACK。否则,对方为了确认已发送的ACK,还要确认已发送的ACK的ACK,这就是一个死循环,永无止息。
所以为了避免这个死循环,一律不允许对对方的裸ACK报文进行确认(通过发送一个ACK报文)。
为什么要三次握手?
三次握手是相对于两次握手而言的。
两次握手
两次握手是指,当客户端期望与服务端建立连接时,客户端向服务端发送一个SYN报文段,服务端收到后,向客户端发送一个ACK报文段。当客户端收到后,双方都会认为这个连接建立成功了。
三次握手解决的问题
不采用两次握手,而使用三次握手,来作为连接建立完成的标志,是为了避免已失效的客户端连接请求报文段(因为网络延迟问题)突然又传送到了服务端,因而建立了一个不被期望的客户端-服务端连接的情况。
“已失效的连接请求报文段”
“已失效的连接请求报文段”的产生在这样一种情况:客户端发出的第一个连接请求报文段(SYN报文段)并没有丢包(丢包是指,因为某些原因被中途经过的某个网络节点将这个包丢弃了),而是在某个网络结点长时间地滞留了,以至于延误(delay)到连接释放以后的某个时间点才到达服务端。
本来这是一个早已失效的报文段,对于客户端而言,也不希望再次建立与服务端的连接了。
但服务端收到此过时的连接请求报文段后,就误认为是客户端再次发出的一个新的连接请求。于是就向客户端发出确认报文段(ACK报文段),同意建立连接。假设不采用“三次握手”,而采用两次握手,那么这时,只要服务端发出确认报文段(ACK报文段)后,服务端就会认为新的连接已经建立完成了。
而事实上,这个建立完成的连接,并不是被客户端期望的,因此客户端不会利用这个连接,因而也不会向服务端通过这个连接来发送任何数据。
前面也提到了,由于服务端认为新的连接已经建立完成了,因而会一直苦苦地等待数据,最终服务端的资源就白白浪费掉了。
采用三次握手后:
而采用三次握手后,被延误的连接请求报文段(SYN报文段)到达了服务端后,服务端因无法判断这个报文段是正常的请求还是被延误的请求,因而自然会向客户端发出确认报文段(SYN+ACK报文段)。
然而,对于客户端而言,它能够判断这个连接请求报文段(SYN报文段)是自己之前发送的,但是因为被延误(delay)了,因此自然没有意义,所以不会向服务端发送对应的ACK报文段。
服务端通过依赖一个超时计时机制,由于在较短时间后没有得到客户端发送的ACK报文段,因而就知道客户端并没有真正要求建立连接,最终不会认为这个连接建立完成了。
因此,三次握手避免了在这种场景下,服务端的资源被白白浪费掉的情况。
换一个角度的分析
这个问题的本质是,通信信道不可靠,但是通信双方仍然需要就某个问题达成一致。
而要解决这个问题,无论你在消息中包含什么信息,三次通信是理论上的最小值。 所以三次握手不是TCP本身的要求,而是为了满足"在不可靠信道上可靠地传输信息"这一需求所导致的。
请注意这里的本质需求,信道不可靠,数据传输要可靠。 三次达到了,那后面你想接着握手也好,发数据也好,跟进行可靠信息传输的需求就没关系了。 因此,如果信道是可靠的,即无论什么时候发出消息,对方一定能收到,或者你不关心是否要保证对方收到你的消息,那就能像UDP那样直接发送消息就可以了。
TCP的三次握手
有同学会说,按照这么说,TCP连接应该是四次消息交互啊。
1.A 发送SYN 报文给B,这是第一次报文交互。
2. B发送ACK确认A的SYN报文,这是第二次报文交互
3. B发送自己的SYN报文给A,这是第三次报文交互
4. A需要ACK确认B的SYN报文,这是第四次报文交互
以上的演绎没有问题,但是报文2、3为何要分开发送呢?增加了延迟不说,同时还白白浪费了网络的带宽,完全可以将报文2、3合并起来,不就是在报文2的ACK状态位的位置置“1”就结了吗?
这就是三次消息交互的由来!
Reference
- TCP 为什么是三次握手,而不是两次或四次? - https://mp.weixin.qq.com/s/NIjxgx4NPn7FC4PfkHBAAQ
- TCP 为什么是三次握手,而不是两次或四次? - https://www.zhihu.com/question/24853633