1. TCP协议基础:从协议栈到状态机

TCP 这东西,说白了就是网络通信的「定海神针」。我做了十几年网络编程,见过无数问题最后都追溯到对 TCP 理解不够深。今天咱们就从最底层开始,把 TCP 的骨架搭起来。

1.1 TCP/IP 协议栈:数据包的奇幻漂流

你想想看,你在浏览器里敲个网址,数据是怎么跑到服务器上的?这背后就是 TCP/IP 协议栈在干活。它分四层,每层各司其职:

层级 核心协议 一句话说明
应用层 HTTP、FTP、SSH 你直接打交道的协议,定义数据格式
传输层 TCP、UDP 负责端到端通信,TCP 保证可靠
网络层 IP 寻址和路由,把数据包送到目标机器
网络接口层 以太网、Wi-Fi 物理传输,比特流在网线上跑

数据从应用层一路往下,每经过一层就加个头部——这叫「封装」。接收端再一层层拆开。我记得刚入行时调一个 HTTP 接口死活连不上,抓包一看,IP 层地址写对了,但 TCP 端口写反了。嗯,这种低级错误,犯过一次就记住了。

核心要点:TCP 工作在传输层,它不关心你的数据是 HTTP 还是 FTP,它只保证「我把你给我的字节流,原封不动地送到对面」。

1.2 TCP 报文格式:头部里的秘密

TCP 报文头部最少 20 字节,最多 60 字节。别看它小,里面每个字段都有讲究。我挑几个最重要的说:

  • 源端口 / 目的端口(各 2 字节): 标识通信的进程。IP 地址找机器,端口找进程。
  • 序列号(4 字节): 这是 TCP 可靠传输的基石。每个字节都有编号,接收方靠它排序、去重。
  • 确认号(4 字节): 告诉对方「我收到哪了」,值是期望收到的下一个序列号。
  • 标志位(9 位): 常用的有 SYN、ACK、FIN、RST。握手挥手全靠它们。
  • 窗口大小(2 字节): 流量控制用的,告诉对方「你慢点发,我快撑不住了」。

我的习惯: 调试网络问题时,我第一件事就是抓包看 TCP 头部。序列号是否连续?窗口是不是被填满了?这些信息比看业务日志管用多了。

1.3 三次握手:建立连接的艺术

为什么是三次?不是两次,也不是四次?我当年面试时也被问过这个问题。咱们走一遍流程就明白了:

  1. 客户端 → 服务端:SYN — 「嘿,我想跟你说话,这是我的初始序列号 x」
  2. 服务端 → 客户端:SYN + ACK — 「收到,这是我的初始序列号 y,我确认收到了你的 x」
  3. 客户端 → 服务端:ACK — 「好,我确认收到了你的 y,咱们开聊」

两次不够,因为服务端没法确认客户端收到了自己的 SYN。四次浪费,第三次握手时已经可以捎带数据了。三次刚刚好。

避坑指南: 我曾经遇到过一个线上问题,客户端频繁断连重连,导致服务端有大量半连接(SYN_RCVD 状态)。后来发现是客户端没做连接复用,每次请求都新建连接。解决方案很简单:用连接池。但排查过程花了一整天。

1.4 四次挥手:好聚好散

断开连接比建立连接复杂,因为 TCP 是「全双工」的——两边可以同时收发。所以每一端都要单独关闭自己的写通道:

  1. 主动方 → 被动方:FIN — 「我这边没数据要发了」
  2. 被动方 → 主动方:ACK — 「知道了,但我还有数据要发」
  3. 被动方 → 主动方:FIN — 「我也发完了,拜拜」
  4. 主动方 → 被动方:ACK — 「收到,我关闭了」

注意第二步和第三步之间,被动方可能还在传输数据。这就是为什么挥手要四次,不能合并。

关键点: 主动关闭方在发送最后一个 ACK 后会进入 TIME_WAIT 状态,持续 2MSL(约 2 分钟)。很多人觉得这个状态烦人,但它其实在保护你——防止最后一个 ACK 丢失导致被动方重发 FIN。

1.5 TCP 状态转换图:一张图看懂所有状态

下面这张图是我自己整理的 TCP 状态机。说实话,刚学的时候觉得这图好复杂,后来在项目中排查过几次连接异常后,发现它其实很直观。

TCP 状态转换图 CLOSED SYN_SENT ESTABLISHED FIN_WAIT_1 FIN_WAIT_2 TIME_WAIT CLOSED LISTEN SYN_RCVD ESTABLISHED CLOSE_WAIT LAST_ACK 主动打开 收到 SYN+ACK 主动关闭 收到 ACK 收到 FIN 被动打开 收到 SYN 收到 ACK 收到 FIN 关闭 SYN 到达 SYN+ACK 返回 说明 左侧:主动方(客户端)状态变迁 右侧:被动方(服务端)状态变迁

我的经验: 排查网络问题时,我习惯先看 netstat -an | grep 端口号。如果看到大量 TIME_WAIT,说明客户端频繁创建短连接;如果看到大量 CLOSE_WAIT,说明服务端代码忘了调用 close()。这两种情况我都遇到过,解决方案完全不同。

1.6 常见状态解读:面试和实战都用的上

状态 含义 常见场景
LISTEN 服务端在监听端口 服务启动正常
SYN_SENT 客户端发了 SYN,等回复 连接超时可能卡在这
ESTABLISHED 连接已建立 正常通信中
CLOSE_WAIT 被动方等应用程序关闭 代码没调 close()
TIME_WAIT 主动方等待 2MSL 短连接过多时出现

避坑指南: 我曾经在生产环境遇到过一个问题:服务端 CLOSE_WAIT 状态堆积到几千个,导致文件描述符耗尽,新连接全部被拒绝。查了半天发现是某个第三方库在读取完数据后没有关闭 socket。从那以后,我写网络代码都会在 finally 块里确保 close() 被执行。

好了,TCP 协议基础就讲到这里。这些概念是后面所有内容的地基——Socket 编程、粘包拆包、心跳机制,全都建立在这些状态和流程之上。你先把这张状态转换图记熟,后面实战时会发现,很多问题看一眼状态就猜到了七八分。