1 理论知识

先上一张图,TCP/IP详解第18章的这张图描述了一个正常的三次握手和四次挥手的状态迁移,以及seq、ack序号的变化。

基本状态看图就能了解,本文主要围绕序号的变化进行讲解。

TCP三次握手的seq和ack号的【正确】理解-LMLPHP

1)seq序号

seq的初始值在不同系统实现不一样,一般为随时间增长的值。当seq超过4字节存储空间后从0开始。

在某个方向上传输N个字节的数据,序列号就+N,因此seq用于确认在某个方向上传输的字节数。

如果传输的数据字节为0,即只有首部,那序列号还加吗?当syn 或 fin被置1,序列号也会+1。其他情况(如只有ack)不加。

2)ack序列号

只有ack标志置1才有效。在TCP交互的整个周期,除了syn包外其他所有包ack都被置1。

ack序列号是上次已经成功收到数据字节序号+1,比如上次成功接收了seq为1000的数据,发送的ack序号为1001,表示我seq1000以前的数据我已经成功接收了,我对序列号1001开始的数据感兴趣。

2 实例

TCP三次握手的seq和ack号的【正确】理解-LMLPHP

这是一次http交互的一个例子。①~③为三次握手,④~⑥为数据传送阶段,⑦~⑨为四次挥手过程(这里实际只有三个包,因为⑧结合了fin和ack两个步骤)。

我们看第一个包的seq和ack都是0,这是wireshark为了方便阅读将序号重0开始计算(没有改变实际值),我们看到有字段里有两个带RAW的字段,它们才是序号真实的值。

注:

1)在下面的解析中,我们不会使用raw的值,但为了说明不是从0开始,我们取整数10000和20000作为两端seq的初始值。

2)192.168.31.21记为A,211.158.235.30记为B。

2.1 三次握手过程

序号变化(假设从10000和20000作为初始值):

TCP三次握手的seq和ack号的【正确】理解-LMLPHP

① 首先A向B发一个SYN包,告诉B请求建立连接。seq为初始化的随机值(如何初始化和系统具体实现有关),这里假设为10000,此时ACK序号为0。

② B收到后会发一个对SYN包的确认包(SYN+ACK)回去,表示对第一个SYN包的确认,并继续握手操作。

此时B也以一个随机值来初始化seq(与A无关),这里假设为20000。B的ACK是A的seq加1,即10000+1=10001。表示你的请求我已收到,我这方的序号就从20000开始,由于你的syn包消耗了1字节,我期望下一个收到的序列号从10001开始。

③ A收到SYN+ACK 包后回一个确认包(ACK)通知B连接已建立。

它的seq是上个请求(①)的seq加1,即10000+1=10001,当然也等于上一个包(②)的ACK,ACK是B的seq加1,即20000+1=20001,用于确认收到②(syn被置位,所以也消耗了1字节)。

至此,三次握手完成,一个TCP连接建立完成。

2.2 数据传输阶段

TCP三次握手的seq和ack号的【正确】理解-LMLPHP

④ A发起Get请求,由于上一个③的len=0,且没有syn或fin标志,因此没有消耗seq的值,seq不变还是10001。ACK序号确认的还是上一个收到的②(这段期间也没有收到新的包)。

⑤ B对收到的④发出回应,告诉A我已经收到。由于上一个请求②消耗了1字节,这里seq为20001。ACK为④的seq+len=10001+183=10184。

⑥ B发送200ok,由于⑤长度为0没有消耗seq,这里seq仍然为20001,ACK序号确认的还是上一个收到的④。

2.2 四次挥手

计算过程基本相同,几个注意点是:

1)A对⑥的确认合并到第一个挥手包里了。

2)⑧将FIN和ACK原本的两次挥手合并。

TCP三次握手的seq和ack号的【正确】理解-LMLPHP

⑦ 确认⑥,并发出挥手。

⑧ 确认⑦,并发出挥手。⑦长度0,但是有FIN标志,消耗1字节,ACK=10184+1=10185。

⑨ 确认⑧,虽然⑧长度为0,但是有FIN标志,消耗1字节,ACK=20155+1=20156。至此挥手完成,TCP整个生命周期结束。

05-25 07:59