TCP建立连接为什么进行三次握手?

时间:2022-09-01 10:18:14

面试被问到这个问题,当时没回答上来,网络上查了很久,感觉还是这个比较全面,虽然文章并没有说清楚,但结合文章及后面的评论就差不多搞懂了。

TCP是基于IP的虚电路可靠的全双工通信服务,基本上可以分为链接建立,数据传输,链接拆除三个阶段。

为什么链接建立阶段采用三次握手机制?

先约定两个名字。A代表链接建立的发起方,B代表链接建立的接受方。

三次握手是指A向B发送一个Sync,B向A回送Ack+Sync,A再向B发送Ack,这样一个过程。
通过这个过程,TCP的全双工的虚电路服务就建立起来了。
为什么是三次握手?
我认为原因是TCP链接逻辑上区分成两个通道,一个通道用于A-》B,另一个通道用于B-》A。这一点从链接建立阶段看不明晰,但是链接拆除阶段非常清楚。我们

知道TCP链接的拆除是所谓的优雅拆除,任何一方都可以拆除自己发往对方数据的那个通道,而同时还要保证对方发往自己的数据能被正常处理。所以TCP链接拆除就要经历一个半链接的阶段。用四个协议数据包进行拆除。
再回过头看看链接建立阶段,就非常明确的看出来,这三次握手确实是用于建立这个链路上的两个通道的。
那么为什么要三次握手?最近看到弯曲评论上有一篇文章,说TCP链接甚至可以建立半链接,也就是只建立一个通道,数据只能单向流动。这时候的三次握手就退化成两次握手,一次Sync以及一个对应的Ack。[同时文章指出,Socket
API并没有提供建立半链接的能力,但是下层的协议确实具有]
所以说三次握手也不是必需的。
我们暂且不关心建立半链接。
究竟为什么需要三次握手?
从逻辑上讲,三次握手是最省的建立TCP链路[两个通道]规程机制了,所以就应该三次握手。
但是,实际上我们必须进行三次握手么?我们知道UDP是数据报服务,也就是无连接的服务。其实无连接的服务也可以看作某种连接服务,比如:看做链接建立、数据传输、链接拆除在一个协议数据包中完成的快速短暂链接服务。这就是说,我们可以复用数据包。有了这个启示,我们就可能对三次握手进行更进一步的优化,实际上,可以优化到没有任何纯协议数据包,只有业务数据包,只要把这几个协议数据包复用于初始数据传输包即可。
但是为什么TCP链接要三次握手进行建立?
我个人认为其实没有理由非得如此。不过我们可以通过TCP的拥塞控制机制得到一些启示。TCP的拥塞控制由“慢启动(Slow
start)”和“拥塞避免(Congestion avoidance)”,“快速重传(Fast retransmit)”、“快速恢复(Fast
Recovery)”,选 择性应答( selective
acknowledgement,SACK)等一系列机制组成的。其中慢启动是TCP链接建立以后,开始发送数据时的策略。为了避免TCP拥塞,采用缓慢提高TCP数据传输速率的办法。所以三次握手我猜想也是为了减缓TCP链接的建立,就如同古代的击鼓伸冤,你可以击鼓伸冤,但在此之前你得过好几关,比如跪带刺的铁板凳等等……,这样的目的是为了提高击鼓伸冤的门槛,免的有人恶意的利用这种机制——除非你愿意为你的冤情付出相应的代价[这也说明冤情沉重],否则你的冤屈不必申述。
当然,有人说三次握手是为了约定初始序号(ISN,Initial Sequence
Number,这儿的序号并不是按照数据包自然数序排列的,其实叫成偏移Offset更合理,因为它其实是指出这个包携带的数据在整个TCP数据流中的偏移量的,也就是说,如果一个包序号为x,而携带的数据长度为y,那么下一个包的序号为x
+
y,如果达到了最大值,就绕到0那儿开始回环。顺便说一句,最初序号是32bit的,现在提升到64bit了,因为要保证协议栈能识别出相同序号的不同的包,不能溢出回环的太快,否则ack的语义不清晰),这当然是对的,三次握手确实约定了初始序号,但是约定初始序号不是为什么是三次握手的原因。实际上正如我前面所说的,没有握手也可以约定初始序号。[在网上找到这样一篇论文,内容看不了,但应该大致跟我的复用数据包的设想差不多,地址:
http://d.wanfangdata.com.cn/Periodical_czsfgdzkxxxb200902019.aspx]
归根结底,TCP链接的建立、使用、拆除三阶段是清晰的分离的,链接建立阶段是独立的,也不能用于传递数据,当然,数据传递阶段也没有建立链接的功能。
那么现在还是那个问题:为什么需要三次握手?

看评论:

1.

这是通信当中的基本问题, 看明白Two Generals' Problem:
http://en.wikipedia.org/wiki/Two_Generals'_Problem
就很容易明白为什么三次握手是必须的了.
这个问题的本质是, 通过一个不完全可靠的信道, 最少需要几次消息传输, 信道两边的人能够对一个问题达成一致. 对于TCP来说, 无论有没有初始
序号的要求, 想要两边都同意开始传出数据, 就至少需要3次消息的交换:
0次: 显然不行
1次: A->B, A不知道B是否同意
2次: A->B, B->A. B不知道A是否收到自己的消息, 因为信道不完全可靠
3次: A->B, B->A, A->B. 两边都收到了对方的ACK, 意味着各自都了解了对方的意图, 从而可以对是否开始通信这个最简单的问题
达成一致. 

2.

这种主考官很好对付,我就遇到过类似的。直接就说三次握手的协议是理论结果和工程实践结合后,选择的一个折中方案,在互联网出现之初,这是一个比较好的方案。主

考听明白的话,应该没兴趣问下去了。

对于现在的互联网,三次握手其实已经落后了,效率低下。所以现在才有不少新的号称高性能面向连接的协议出来讨生活。

3.

TCP的三次握手是妇孺皆知的事情,似乎没有什么可以讨论的。笔者也曾经面试别人,提出过下面的问题:

“TCP为何要三次握手,四次断开?”

笔者期望的答案是:

TCP的连接建立一定要双向,所以第二个报文SYN/ACK齐发,而断开的时候可以保持半连接,即断开一半。理论依据是笔者根据Socket的实现得出的,Socket之中没有给出建立半连接的API,但是给出了shutdown可以关闭半连接。


我的理解:

tcp要建立一个全双工的连接,也就是可以理解为两个单独的通道,A->B和B->A。当A发给B 数据包(syn)时,可以理解为要和B建立链接,B回复给A数据包(ACK+SYN)时,即B同意建立A->B的连接,同时询问要和A建立链接,然后A回复B数据包(ACK)表示同意建立B->A的连接。这样一个全双工的连接便建立起来了。