TCP包分析

TCP

原理

核心特点:
面向连接:通信前必须通过“三次握手”建立连接,通信结束后通过“四次挥手”释放连接;
可靠传输:通过“确认机制、重传机制、排序机制、流量控制、拥塞控制”,保证数据一定送达、顺序正确,丢包后会自动重传;
头部开销大:TCP头部最小20字节,最大60字节(含选项字段),比UDP复杂;
有端口:通过源端口和目的端口,标识应用程序(如HTTP使用80号端口,HTTPS使用443号端口);
面向字节流:将用户数据拆分为多个字节流,接收方再重组为完整数据,不局限于单个数据报。
核心流程(汇报核心):TCP通信的完整流程分为3个阶段——三次握手(建立连接)→ 数据传输 → 四次挥手(释放连接),实验中抓包可清晰看到这三个阶段的数据包,是汇报的重点。

三次握手

1
2
3
4
5
Client -----SYN seq=0-----> Server

Client <-----ACK+SYN seq=0 ack=1----- Server

Client -----ACK seq=1 ack=1-----> Server

四次挥手

1
2
3
4
5
6
7
Client -----FIN ACK-----> Server

Client <-----ACK----- Server

Client <-----FIN ACK----- Server

Client -----ACK-----> Server

关于四次挥手我这里为了方便理解只写了客户端发起挥手请求
但是事实上TCP是全双工的,抓包可能会出现服务端先发起回收请求,这是正常的

常见的原因

  1. 触发了服务端的 Keep-Aliver 超时(最常见)
    当你访问完一个网站后,页面加载完成了,你没有关闭网页,也没有进行下一次点击。
  • Nginx 默认的 keepalive_timeout 通常是 65秒
  • Apache 默认可能只有 5秒
    当这个倒计时归零,而你没有发新的请求时,服务器就会认为你暂时不需要它了,于是主动发送第一个 FIN 包,发起四次挥手。
  1. 达到了最大请求次数限制
    为了防止某些恶意连接或异常客户端一直占用通道,服务器还会限制一个连接最多能处理多少个 HTTP 请求。
  • 例如 Nginx 的 keepalive_requests 默认是 1001000
  • 当你在同一个页面里加载了太多的静态资源(图片、JS、CSS),只要请求次数达到了这个上限,服务器就会在最后一个响应发完后,主动发起四次挥手断开,让浏览器去重新建连接。
  1. Web 服务器的网关或代理主动断开
    很多现代网站前端有 Nginx 作为反向代理,后端是 Tomcat、Node.js 或 Go。如果后端程序处理完业务关闭了连接,或者代理服务器认为连接闲置了,Nginx 就会代表服务端向你的浏览器发起四次挥手。

序列号和确认号

TCP 把所有要传输的数据(包括控制信息)都看成一个连续的字节流,就像一长串连续的快递包裹。

  • 序列号(Seq):就是每个字节的唯一快递单号,32 位整数,从 0 到 2^32-1 循环使用。
  • 确认号(Ack):就是 “我已经收到了所有单号小于 X 的包裹,接下来请你发单号为 X 的包裹”
  1. SYN(连接请求)和 FIN(断开请求)各占用 1 个序列号:它们是特殊的 “控制包裹”,虽然没有数据,但需要被对方确认,所以必须占一个单号。
  2. 纯 ACK(确认包)不占用序列号:它只是告诉对方 “我收到了”,不需要对方再确认自己。
  3. 双向独立:客户端和服务器各自维护自己的快递单号系统,互不干扰。就像甲给乙寄快递用甲的单号,乙给甲寄快递用乙的单号。

TCP包分析

三次握手

第一个包

  1. Source Port: 13033
    • 源端口号,随机分配
  2. Destination Port: 443
    • 目标端口,服务器端口
  3. Sequence Number: 0
  4. Sequence Number (raw): 1267618160
    • 相对序列号和原始序列号,相对序列号是wireshark为了方便看生成的,原始序列号纯随机生成
  5. Next Sequence Number: 1(relative sequence number)
    • 下一个序列号
  6. Acknowledgment Number: 0
  7. Acknowledgment number (raw): 0
    • 确认号,此时未收到确认所以为0
  8. Flags: 0x002 (SYN)
    • 标志位,SYN=1,其他为0,标识这是SYN包,请求建立连接
  9. Window: 65535
    • 这是客户端通告的接收窗口大小,单位是字节。65535是16位窗口字段的最大值,表示客户端初始愿意接收最多65535字节的未确认数据。
  10. Checksum: 0x4c08 [unverified]
    • 校验和
  11. Urgent Pointer: 0
    • 紧急指针,为0无效
  12. Options: (12 bytes)
    • Maximum segment size(MSS): 最大分段大小,告诉服务器自己能接收的最大 TCP 分段长度 (不包含 TCP 和 IP 首部,(通常1500 - IP首部20 - TCP首部 = 1460 ))
    • No-Operation(NOP): 空操作,用于填充,使选项长度为 4 字节的整数倍
    • Window scale: 窗口缩放因子,用于扩展 TCP 窗口大小。16 位窗口最大只能表示 65535 字节,通过缩放因子可以支持 GB 级别的窗口
    • SACK permitted: 服务器支持选择性确认 (Selective Acknowledgment)
    • Timestamps: 时间戳选项,有两个核心用途:
      更精确地计算往返时间 (RTT)
      防止序列号回绕 (PAWS),解决高速网络中 TCP 序列号快速循环导致的新旧报文混淆问题

第二个包

  1. Flags: 0x012 (SYN,ACK)
    • 这是 TCP 三次握手第二步独有的标志位组合:
    • ACK=1:表示这是一个确认报文,确认客户端的 SYN 连接请求
    • SYN=1:表示服务器也同意建立连接,并同步自己的初始序列号

第三个包

  1. Flags: 0x010 (ACK)
    • 仅做确认

三次握手流程

  1. 客户端主动发起连接,发送 SYN=1 报文,客户端进入 SYN_SENT
  2. 服务端收到 SYN,发送 SYN=1 + ACK=1,服务端进入 SYN_RCVD
  3. 客户端收到报文,发送 ACK=1,客户端进入ESTABLISHED已连接
  4. 服务端收到ACK:进入ESTABLISHED已连接

连接建立完成,开始传数据

关于序列号和确认号

第一个包:客户端 → 服务器(SYN)

  • 客户端独立随机生成自己的初始序列号 ISN(原始值:1267618160),Wireshark 为了方便显示为相对序列号 0
  • 客户端还未收到服务器任何数据,所以确认号初始化为 0
  • 本次消耗序列号:1 个(SYN 标志位)
  • 客户端下一个要发送的序列号:0 + 1 = 1

服务端接收到 SYN 后:

  • 服务端独立随机生成自己的初始序列号 ISN(原始值:4291741377),Wireshark 显示为相对序列号 0
  • 服务端根据公式计算确认号:客户端Seq(0) + 消耗数量(1) = 1
  • 服务端下一个要发送的序列号:0 + 1 = 1

第二个包:服务器 → 客户端(SYN+ACK)

  • 服务端发送自己的 SYN(消耗 1 个序列号)和对客户端 SYN 的确认
  • 序列号:自己的初始序列号0
  • 确认号:刚才计算好的1
  • 本次消耗序列号:1 个(只有 SYN 消耗,ACK 不消耗)

客户端接收到 SYN+ACK 后:

  • 客户端根据公式计算确认号:服务端Seq(0) + 消耗数量(1) = 1
  • 客户端下一个要发送的序列号:还是1(因为上一个包已经消耗完了 0 号)

第三个包:客户端 → 服务器(纯 ACK)

  • 客户端发送对服务端 SYN 的确认
  • 序列号:自己上一个包的下一个序列号1
  • 确认号:刚才计算好的1
  • 本次消耗序列号:0 个(纯 ACK 不消耗)
  • 客户端下一个要发送的序列号:还是 1(因为没消耗)

服务端接收到 ACK 后:

  • 服务端下一个要发送的序列号:还是1
  • 服务端的确认号:保持1(因为纯 ACK 不消耗序列号,不需要更新)

往后的包就是如此交替往复,参照公式

1
确认号(Ack) = 对方本次发送的起始序列号(Seq) + 对方本次消耗的序列号总数

对方本次消耗的序列号总数 =

  • 如果是 SYN 包:1
  • 如果是 FIN 包:1
  • 如果是数据包:TCP Segment Len(数据长度)
  • 如果是纯 ACK 包:0

四次挥手

四次挥手包结构和三次握手结构类似

挥手流程:

  1. 客户端发送FIN=1并进入FIN_WAIT_1状态,等待服务器的确认
  2. 服务端收到FIN=1后立刻发送ACK=1,服务器进入CLOSE_WAIT状态
  3. 客户端收到这个ACK后,进入FIN_WAIT_2状态,继续等待服务器的FIN报文
  4. 服务器TCP发送一个FIN=1的报文段,服务器进入LAST_ACK状态
  5. 客户端收到服务器的 FIN 报文后,立即发送一个ACK=1的确认报文段,客户端进入TIME_WAIT状态
  6. 服务器收到这个ACK后,立即进入CLOSED状态,连接正式关闭

TCP包分析
http://www.ming-ice-tea.top/2026/05/17/TCP包分析/
作者
Ming
发布于
2026年5月17日
许可协议