万能公式
4.23 TCP 序列号和 确认号是如何变化 的?
只知道三次握手和四次挥手过程中,ACK 报文中确认号要 +1 ,然后数据传输中 TCP 序列号和 确认
号的变化 就不知道了。
三次握手中 TCP 序列号和 确认号的变化
数据传输中 TCP 序列号和 确认号的变化
四次挥手中 TCP 序列号和 确认号的变化
万能公式
我根据经验总结了一条万能公式。
发送的 TCP 报文:
公式一:序列号 = 上一次发送的序列号 + len (数据⻓度)。特殊情况,如果上一次发送的报文是 SYN 报文或者 FIN 报文,则改为 上一次发送的序列号 + 1 。
公式二:确认号 = 上一次收到的报文中的序列号 + len (数据⻓度)。特殊情况,如果收到的是SYN 报文或者 FIN 报文,则改为上 一次收到的报文中的序列号 + 1 。
可能有点抽象,接下来举一些实际的场景,加深对这个万 能公式的理解。
先给大家看看 TCP 序列号和 确认号在 TCP 头部的哪个位置。可以看到,这两个 字段都是 32 位。

这里重点关注这三个 字段的作用:
序列号:在建立连接时由内核生成的随机数作为其初始值,通过 SYN 报文传给接收端主机,每
发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。
确认号:指下一次「期望 」收到的数据的序列号,发送端收到接收方发来的 ACK 确认报文以
后,就可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。
** 控制位:** 用来标识 TCP 报文是什么 类型的报文,比如是 SYN 报文、数据报文、ACK 报文,
FIN 报文等。
三次握手阶段的变化
先来说说 三次握手中 TCP 序列号和 确认号的变化 。
假设客⼾端的初始化序列号为 client_isn ,服务端的初始化序列号为 server_isn ,TCP 三次握手的流
程如下:

在这里我们重点关注,下面这两个 过程。
服务端收到客⼾端的 SYN 报文后,会将 SYN-ACK 报文(第二次握手报文)中序列号和 确认号分别设置为:
序列号设置为服务端随机初始化的序列号 server_isn 。
确认号设置为 client_isn + 1 ,服务端上一次收到的报文是客⼾端发来的 SYN 报文,该报文的
seq = client_isn ,那么根据公式 2(
确认号 =上一次收到的报文中的序列号 + len
。特殊情况,
如果收到的是 SYN
报文或者 FIN
报文,则改为 + 1 ),可以得出当前确认号 = client_isn + 1 。客⼾端收到服务端的 SYN-ACK 报文后,会将 ACK 报文(第三次握手报文)中序列号和 确认号分别设置为:
序列号设置为 client_isn + 1 。客⼾端上一次发送报文是 SYN 报文,SYN 的序列号为
client_isn ,根据公式 1(
序列号 =上一次发送的序列号 + len
。特殊情况,如果上一次发送的报
文是 SYN
报文或者 FIN
报文,则改为 + 1 ),所以当前的序列号为 client_isn + 1 。确认号设置为 server_isn + 1 ,客⼾端上一次收到的报文是服务端发来的 SYN-ACK 报文,该报
文的 seq = server_isn ,那么根据公式 2(
确认号 =收到的报文中的序列号 + len
。特殊情况,
如果收到的是 SYN
报文或者 FIN
报文,则改为 + 1 ),可以得出当前确认号 = server_isn + 1 。为什么 第二次和第三次握手报文中的确认号是将对 方的序列号 + 1 后作为确认号呢 ?
SYN 报文是特殊的 TCP 报文,用于建立连接时使用,虽然 SYN 报文不携带用⼾数据,但是 TCP
将 SYN 报文视为 1 字节的数据,当对方收到了 SYN 报文后,在回复 ACK 报文时,就需要将 ACK
报文中的确认号设置为 SYN 的序列号 + 1 ,这样做是有两个 目的:
告诉对方,我方已经收到 SYN 报文。
告诉对方,我方下一次「期望 」收到的报文的序列号为此确认号,比如客⼾端与服务端完成三
次握手之后,服务端接下来期望 收到的是序列号为 client_isn + 1 的 TCP 数据报文。
数据传输阶段的变化
完成了,三次握手后,客⼾端就可以发送第一个 ** **TCP 数据报文了,假设客⼾端即将要发送 10
字节的数据,流程图下:

客⼾端发送 10 字节的数据,通常 TCP 数据报文的控制位是 [PSH, ACK] ,此时该 TCP 数据报文的
序列号和 确认号分别 设置为:
序列号设置为 client_isn + 1 。客⼾端上一次发送报文是 ACK 报文(第三次握手),该报文的
seq = client_isn + 1 ,由于是一个单纯的 ACK 报文,没有携带用⼾数据,所以 len = 0 。根据公式 1(
序列号 =
上一次发送的序列号 + len ),可以得出当前的序列号为 client_isn + 1 + 0 ,即
client_isn + 1 。
确认号设置为 server_isn + 1 。没错,还是和第三次握手的 ACK 报文的确认号一样,这是因为客⼾端三次握手之后,发送 TCP 数据报文 之前,如果没有收到服务端的 TCP 数据报文,确认号还
是延用上一次的,其实根据公式 2 你也能得到这个结论。
可以看到,客⼾端与服务端完成 TCP 三次握手后,发送的第一个 「TCP 数据报文的序列号和 确认
号」都是和「第三次握手的 ACK 报文中序列号和 确认号」一样的。
接着,当服务端收到客⼾端 10 字节的 TCP 数据报文后,就需要回复一个 ACK 报文,此时该报文
的序列号和 确认号分别 设置为:
序列号设置为 server_isn + 1 。服务端上一次发送报文是 SYN-ACK 报文,序列号为 server_isn ,
根据公式 1(
序列号 =上一次发送的序列号 + len
。特殊情况,如果上一次发送的报文是 SYN
报文或者 FIN
报文,则改为 + 1 ),所以当前的序列号为 server_isn + 1 。确认号设置为 client_isn + 11 。服务端上一次收到的报文是客⼾端发来的 10 字节 TCP 数据报
文,该报文的 seq = client_isn + 1 ,len = 10 。根据公式 2(
确认号 =上一次收到的报文中的序
列号 + len ),也就是将「收到的 TCP 数据报文中的序列号 client_isn + 1 ,再加上 10 (len = 10 ) 」的值作为了 确认号,表示自己收到了该 10 字节的数据报文。之前有读者问,如果客⼾端发送的第三次握手 ACK 报文丢失了,处于 SYN_RCVD 状态服务
端收到了客⼾端第 一个 TCP 数据报文会发生什么 ?
刚才前面我也说了,发送的第一个 「TCP 数据报文的序列号和 确认号」都是和「第三次握手的
ACK 报文中序列号和 确认号」一样的,并且该 TCP 数据报文也有将 ACK 标记位置为 1。如下图:

所以,服务端收到这个数据报文,是可以正常完成连接的建立,然后就可以正常接收这个数据包
了。
四次挥手阶段的变化
最后,我们来看看 四次挥手阶段中,序列号和 确认号的变化 。
数据传输阶段结束后,客⼾端发起了 FIN 报文,请求服务端端 开该 TCP 连接,此时就进入了 TCP
四次挥手阶段,如下图。

客⼾端发送的第一次挥手的序列号和 确认号分别 设置为:
序列号设置为 client_isn + 11 。客⼾端上一次发送的报文是 [PSH, ACK] ,该报文的 seq = client_isn + 1, len = 10 ,根据公式 1(
序列号 =上一次发送的序列号 + len ),可以得出当前的
序列号为 client_isn + 11 。确认号设置为 server_isn + 1 。客⼾端上一次收到的报文是服务端发来的 ACK 报文,该报文的
seq = server_isn + 1 ,是单纯的 ACK 报文,不携带用⼾数据,所以 len 为 0。那么根据公式 2
(确认号 = 上一次收到的序列号 + len ),可以得出当前的确认号为 server_isn + 1 + 0 (len = 0),也就是 server_isn + 1 。服务端发送的第二次挥手的序列号和 确认号分别 设置为:
序列号设置为 server_isn + 1 。服务端上一次发送的报文是 ACK 报文,该报文的 seq = server_isn + 1 ,而该报文是单纯的 ACK 报文,不携带用⼾数据,所以 len 为 0,根据公式 1
(
序列号 =
上一次发送的序列号 + len ),可以得出当前的序列号为 server_isn + 1 + 0 (len = 0),也就是 server_isn + 1 。确认号设置为 client_isn + 12 。服务端上一次收到的报文是客⼾端发来的 FIN 报文,该报文的
seq = client_isn + 11 ,根据公式 2(_确认号=上一次_
收到的序列号 + len
,特殊情况,如果收
到报文是 SYN
报文或者 FIN
报文,则改为 + 1 ),可以得出当前的确认号为 client_isn + 11 + 1,也就是 client_isn + 12 。服务端发送的第三次挥手的序列号和 确认号还是和第二次挥手中的序列号和 确认号一样。
序列号设置为 server_isn + 1 。
确认号设置为 client_isn + 12 。客⼾端发送的四次挥手的序列号和 确认号分别 设置为:
序列号设置为 client_isn + 12 。客⼾端上一次发送的报文是 FIN 报文,该报文的 seq = client_isn + 11 ,根据公式 1(
序列号 =上一次发送的序列号 + len
。特殊情况,如果收到报文是 SYN
报
文或者 FIN
报文,则改为 + 1 ),可以得出当前的序列号为 client_isn + 11 + 1 ,也就是
client_isn + 12 。确认号设置为 server_isn + 2 。客⼾端上一次收到的报文是服务端发来的 FIN 报文,该报文的
seq = server_isn + 1 ,根据公式 2(_确认号 =上一次_
收到的序列号 + len
,特殊情况,如果收
到报文是 SYN
报文或者 FIN
报文,则改为 + 1 ),可以得出当前的确认号为 server_isn + 1 + 1,也就是 server_isn + 2 。实际抓包图
在这里贴一个,实际过程中的抓包图。

套入我的万能公式,发送的 TCP 报文:
公式一:序列号 = 上一次发送的序列号 + len (数据⻓度)。特殊情况,如果上一次发送的报文是 SYN 报文或者 FIN 报文,则改为 上一次发送的序列号 + 1 。
公式二:确认号 = 上一次收到的报文中的序列号 + len (数据⻓度)。特殊情况,如果收到的是SYN 报文或者 FIN 报文,则改为上 一次收到的报文中的序列号 + 1 。
懂了这套公式之后,相信你 在看这类的抓包图中序列号和 确认号的变化 的时候,就不会没有逻辑
了。
