目录
前言
最近在知乎上看到一个问题——TCP为什么是三次握手,而不是两次或四次?这个问题我原先也发过博客,之前我的回答是:为了防止两次握手情况下已失效的连接请求报文段突然有传送到服务端,而产生了错误(主要是参考谢希仁版的《计算机网络》)。以及四次握手不能有效的增加TCP连接的安全性,反而让客户端等待的时间变长。但是在看了车小胖的回答后,我才真的理解了为什么TCP连接要采用三次握手以及三次握手握了哪些信息。以下会有很多他所举得例子,我也会抓一些包向大家展示三次握手中的序号和确认号的变化。而谢希仁版的《计算机网络》给我们的例子也没有问题,只是问题的切入点不一样。
先要弄清楚两个概念
(1)序号:占4个字节。TCP是面向字节流的,所以TCP连接中传送的数据流中的每一个字节都编上一个序号。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。例如,一个报文段的序号字段值是200,携带的数据总共有100字节,表明这个报文段的数据的最后一个字节的序号是299,所以下一个报文段的数据序号应从300开始。
(2)确认号:占4个字节,是期望收到对方的下一个报文段的数据的第一个字节的序号。若确认号为N,表明前N-1的所有数据都已经正确接收。例如,B正确的收到了A发送过来的一个报文段,其序号字段是指501,而数据长度是200字节(序号501~700),表明B正确的收到了A发送的序号700之前的数据。因此B希望收到A的下一个数据序号是701,所以B在发送给A的确认报文段中应把确认号设置成701。
TCP可靠传输的精髓:TCP连接的一方A,由操作系统动态随机选取一个32位长的***(Initial Sequence Number),假设A的初始***为1000,以该***为起始点,对自己将要发送的每个字节的数据进行编号,1000,1001,1002,1003…,并把自己的初始***ISN告诉B,让B有一个思想准备,什么样编号的数据是合法的,什么编号是非法的,比如编号900就是非法的,同时B还可以对A每一个编号的字节数据进行确认。如果A收到B确认编号为2000,则意味着字节编号为1000-1999,共1000个字节已经安全到达。
同理B也是类似的操作,假设B的初始***ISN为2000,以该***为原点,对自己将要发送的每个字节的数据进行编号,2000,2001,2002,2003…,并把自己的初始***ISN告诉A,以便A可以确认B发送的每一个字节。如果B收到A确认编号为4000,则意味着字节编号为2000-3999,共2000个字节已经安全到达。
一句话概括,TCP连接握手,握的是啥?
通信双方数据的***的起始点!
抓包分析
建立连接过程中的Sequence number
图中的[1687]和[1688]是三次握手中的后两步。从[1687]这个报文可以看到Sequence number的值为0,但是下方相应报文段的内容是[7f a6 f7 31]。
[1688]的Sequence number值为1,但是下方相应报文段的内容是[12 2d d7 1d](起始点)。
[8197]发送的确认报文Sequence number为10147,即有[12 2d fe bf](65215) - [12 2d d7 1d](55069) = 10147 - 1。这说明在三次握手的过程中,通信双方就将自己的序号的起始点告知给了对方,并且对方将这个数字记录了下来,用于之后的通信。此时报文段中的实际值不代表传输过程中的序号值的真实含义。
为什么TCP在建立连接的时候不能每次选择相同的、固定的初始序号?
答:(1)假如A和B频繁地建立连接,传送一些TCP报文段后再释放连接,然后又不断的建立新的连接、传送报文段和释放连接。
(2)假如每一次建立连接时,主机A都选择相同的、固定的初始序号,如1。
(3)若主机A发送出的某些TCP报文段在网络中会滞留较长的时间,以致造成主机A超时重传这些TCP报文段。
(4)若有一些在网络中滞留时间较长的TCP报文段最后终于到达了主机B,但这时传送该报文段的那个连接早已释放了,而在到达主机B时的TCP连接是一条新的TCP连接。
以上这些情况可能会导致在新的TCP连接中的主机B有可能会接收在旧的连接传送的、已经没有意义的、过时的TCP报文段(因为这个TCP报文段的序号有可能正好处于新的连接所使用的序号范围内)。因为必须使得迟到的TCP报文段的序号不在新的连接中使用的序号范围内。所以,TCP在建立新的连接时所选择的初始序号一定要和前面的一些连接所使用过的序号不一样。因此,不同的TCP连接不能使用相同的初始序号。
数据传输过程中的Sequence number
以下用A代表客户端,B代表服务器。
[8197]A->B Seq = 10147 Ack = 10391980 Len = 0 没有传输数据,希望下一个收到的报文段的序号为10391980。
[8198]B->A Seq = 10391980 Ack = 10147 Len = 2800 因为[8197]没有数据,所以B希望收到的序号还是10147。此次B发送的数据从序号10391980开始,共2800个数据。
[8199]A->B Seq = 10147 Ack = 10394780 Len = 0 没有传输数据,希望下一个收到的报文段的序号为10394780(10391980+2800)。
[8200]B->A Seq = 10394780 Ack = 10147 Len = 1400 因为[8199]没有数据,所以B希望收到的序号还是10147。此次B发送的数据从序号10394780开始,共1400个数据。
[8201]B->A Seq = 10396180 Ack = 10147 Len = 1400 此次B发送的数据从序号10396180开始,共1400个数据。
[8202]A->B Seq = 10147 Ack = 10397580 Len = 0 确认号10397580 = 10396180 + 1400表明[8201]之前的数据已经正确接收了。
为什么TCP建立连接不是两次握手也不是四次握手?
弄懂了TCP握手握的是什么后,再来分析握几次。
四次握手的过程:
1.1 A 发送同步信号SYN + A's Initial sequence number
1.2 B 确认收到A的同步信号,并记录A's ISN 到本地,命名 B's ACK sequence number
1.3 B发送同步信号SYN + B's Initial sequence number
1.4 A确认收到B的同步信号,并记录B's ISN 到本地,命名 A's ACK sequence number并返回ACK。
很显然1.2和1.3 这两个步骤可以合并,只需要三次握手,可以提高连接的速度与效率。
如果A发给B的确认丢了,该如何?A会超时重传这个ACK吗?
答:A不会超时重传,因为TCP不会为没有数据的ACK超时重传。处理方法是:B如果没有收到A的ACK,会超时重传自己的SYN同步信号,直到收到A的ACK为止。
二次握手的过程:
2.1 A 发送同步信号SYN + A's Initial sequence number
2.2 B发送同步信号SYN + B's Initial sequence number + B's ACK sequence number
这里有一个问题,A与B就A的初始***达成了一致,这里是1000。但是B无法知道A是否已经接收到自己的同步信号,如果这个同步信号丢失了,A和B就B的初始***将无法达成一致。
于是TCP的设计者将SYN这个同步标志位SYN设计成占用一个字节的编号(FIN标志位也是),既然是一个字节的数据,按照TCP对有数据的TCP segment 必须确认的原则,所以在这里A必须给B一个确认,以确认A已经接收到B的同步信号。
补充阅读
三次握手中的第三个包丢失,即A发给B的ACK中途被丢,没有到达B,会有什么情况?
A发完ACK,单方面认为TCP为 Established状态,而B显然认为TCP为Active状态:
a. 假定此时双方都没有数据发送,B会周期性超时重传,直到收到A的确认,收到之后B的TCP 连接也为 Established状态,双向可以发包。
b. 假定此时A有数据发送,B收到A的 Data + ACK,自然会切换为established 状态,并接受A的Data。
c. 假定B有数据发送,数据发送不了,会一直周期性超时重传SYN + ACK,直到收到A的确认才可以发送数据。