1. TCP协议基础:从底层说起

大家好,我是你们的老朋友。今天咱们开始聊TCP,这个网络编程里绕不开的坎儿。

说实话,我刚开始做网络编程那会儿,对TCP也是一知半解。直到有一次线上服务莫名其妙丢数据,排查了整整两天,最后发现是对TCP的可靠性机制理解不到位。从那以后,我养成了一个习惯:写网络代码之前,先把TCP协议栈模型在脑子里过一遍。

好,咱们不废话,直接开干。

1.1 TCP/IP协议栈模型

你想想看,数据从你的应用程序发出去,到对方收到,中间经历了什么?

这就是协议栈干的事。我习惯把它理解成四层快递系统:

层级 名称 我眼中的角色
第4层 应用层 你写的代码,比如HTTP、FTP
第3层 传输层 TCP/UDP,负责端到端通信
第2层 网络层 IP协议,负责寻址和路由
第1层 网络接口层 网卡、网线,物理传输

每一层只关心自己的事。应用层不管数据怎么打包,传输层不管数据走哪条路。这就是分层解耦的思想。

核心要点:TCP工作在传输层,它给应用层提供的是可靠的、面向连接的、基于字节流的服务。

嗯,这里要注意:TCP是面向字节流的。这句话特别重要,因为它直接导致了我们后面要讲的粘包问题。你发的是字节流,不是消息流——TCP不关心你的消息边界。

1.2 TCP报文格式:解剖麻雀

咱们来看看TCP报文长什么样。说白了,就是数据在传输时的包装盒。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         源端口号            |          目的端口号             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       序列号(32位)                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       确认号(32位)                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 数据偏移 | 保留  | 标志位    |           窗口大小             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         校验和              |          紧急指针               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    选项(可选)                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            数据                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

这里我挑几个关键字段说说:

  • 源端口/目的端口:哪个应用发的,哪个应用收的。端口号范围0-65535,0-1023是知名端口。
  • 序列号:每个字节都有一个编号。我第一次看这个设计时觉得太奢侈了,后来才明白——没有序列号,TCP就没法保证有序传输。
  • 确认号:告诉对方「我收到哪了」。这是TCP可靠性的基石。
  • 标志位:SYN、ACK、FIN、RST、PSH、URG。咱们后面三次握手四次挥手全靠它们。
  • 窗口大小:流量控制用的。告诉对方「你慢点发,我快撑不住了」。

个人经验:我在做高性能服务器时,经常要抓包分析TCP报文。Wireshark是个好工具,建议你们也学会用。有一次我发现客户端一直发RST包,排查下来是对端端口没开——这种问题看代码很难发现,抓包一眼就明白了。

1.3 三次握手与四次挥手

这是面试必考题,但很多人只背了流程,没理解为什么。

三次握手:建立连接

说白了就是双方确认彼此都在线,并且都愿意通信。

客户端                         服务器
  |                              |
  |---- SYN (seq=x) ------------>|  第一次:客户端说「我要连你」
  |                              |
  |<--- SYN+ACK (seq=y, ack=x+1)-|  第二次:服务器说「收到,我也准备好了」
  |                              |
  |---- ACK (seq=x+1, ack=y+1)->|  第三次:客户端说「好,开始吧」
  |                              |

为什么是三次,不是两次?

我举个例子你就明白了。假设只有两次握手:客户端发了SYN,服务器回了ACK,连接就建立了。但如果这个SYN在网络中延迟了,客户端等不及重发了一个,服务器收到后建立了连接。等数据传输完关闭后,那个延迟的SYN才到达服务器——服务器以为又要建立新连接,白白浪费资源。

三次握手就是为了防止「已失效的连接请求」到达服务器。第三次握手时,客户端会带上一个确认,服务器收到后才真正建立连接。如果那个延迟的SYN到达,服务器回SYN+ACK,但客户端不会理它——因为客户端没发过这个SYN。

避坑指南:我曾经遇到过一个线上问题,客户端频繁建立短连接,导致服务器TIME_WAIT状态堆积。最后把端口号都用完了,新连接建立失败。解决方案是复用连接或调整TIME_WAIT超时时间。这个后面讲粘包时还会提到。

四次挥手:断开连接

断开比建立复杂,因为双方都要说再见。

客户端                         服务器
  |                              |
  |---- FIN (seq=u) ----------->|  第一次:客户端说「我不发了」
  |                              |
  |<--- ACK (ack=u+1) ----------|  第二次:服务器说「知道了」
  |                              |
  |<--- FIN (seq=v) ------------|  第三次:服务器说「我也不发了」
  |                              |
  |---- ACK (ack=v+1) --------->|  第四次:客户端说「好的,再见」
  |                              |

注意,第二次和第三次之间可能有时间间隔。因为服务器可能还有数据没发完,它得先把数据发完,再发FIN。

这里有个关键状态:TIME_WAIT。主动关闭的一方(通常是客户端)在发送最后一个ACK后,会进入TIME_WAIT状态,持续2MSL(最大报文生存时间)。

为什么要等2MSL?

  • 万一最后一个ACK丢了,服务器会重发FIN,客户端得能重新发ACK
  • 确保所有旧报文在网络中消失,避免影响新连接

1.4 TCP的可靠性机制

TCP号称可靠传输,它到底是怎么做到的?我总结为四大法宝:

1. 确认与重传

发送方发了数据,必须收到接收方的ACK才算完。如果超时没收到,就重传。

这里有个细节:超时时间怎么算?TCP会动态计算RTT(往返时间),然后根据RTT设置超时时间。太短了容易误判,太长了影响效率。

我踩过的坑:有一次我写了一个自定义协议,在应用层也做了ACK确认。结果TCP层也在做同样的事,双重确认导致性能下降30%。后来我意识到:TCP已经提供了可靠传输,应用层不需要再重复造轮子。除非你需要更细粒度的控制。

2. 流量控制

接收方通过窗口大小字段告诉发送方:「我还能收多少数据」。发送方不能超过这个窗口大小。

这就像两个人倒水:你倒得太快,对方杯子满了就溢出了。TCP的流量控制就是让对方告诉你「我的杯子多大,你慢慢倒」。

3. 拥塞控制

流量控制是端到端的,拥塞控制是网络层面的。TCP通过慢启动、拥塞避免、快重传、快恢复四个算法来应对网络拥塞。

简单说:

  • 慢启动:刚开始发得慢,慢慢加速
  • 拥塞避免:达到阈值后,线性增长
  • 快重传:收到三个重复ACK,立即重传,不等超时
  • 快恢复:重传后,降低发送速度,但不回到慢启动

4. 有序传输

TCP给每个字节编号(序列号),接收方按序列号重组数据。即使数据乱序到达,也能恢复成正确的顺序。

你想想看,如果没有序列号,数据包走不同的路径到达,顺序全乱了,那还怎么用?

本章小结

好了,这一章的内容就这些。咱们把TCP的底子打好了,后面讲粘包拆包时你才能理解——为什么TCP是流式协议?为什么它不保留消息边界?

这些问题的答案,都在这一章里。

给新手的建议:别急着写代码,先把TCP协议栈模型和报文格式搞明白。我见过太多人一上来就写socket,结果遇到问题一脸懵。基础不牢,地动山摇。

TCP协议知识体系 TCP协议 基础 协议栈模型 四层结构 报文格式 头部字段详解 连接管理 三次握手/四次挥手 可靠性机制 四大法宝 应用层 传输层 网络层 网络接口层 序列号/确认号 标志位(SYN/ACK/FIN) 窗口大小 校验和 三次握手 四次挥手 TIME_WAIT状态 确认与重传 流量控制 拥塞控制

可可精品资料库