深入理解TCP协议(一):TCP报文格式详解

目录

[一、 TCP 协议是什么](#一、 TCP 协议是什么)

[二、 TCP 报文格式](#二、 TCP 报文格式)

[16 位源端口号](#16 位源端口号)

[4 位首部长度和 16 位目的端口号](#4 位首部长度和 16 位目的端口号)

[32 位序号和 32 位确认序号](#32 位序号和 32 位确认序号)

[16 位窗口大小](#16 位窗口大小)

标志位

[ACK 标志位](#ACK 标志位)

[SYN 标志位](#SYN 标志位)

[FIN 标志位](#FIN 标志位)

[RST 标志位](#RST 标志位)

[PSH 标志位](#PSH 标志位)

[URG 标志位](#URG 标志位)

[16 位紧急指针](#16 位紧急指针)

16位校验和

选项字段

[常见 TCP 选项](#常见 TCP 选项)

[1. MSS(Maximum Segment Size)](#1. MSS(Maximum Segment Size))

[2. 窗口扩大选项(Window Scale)](#2. 窗口扩大选项(Window Scale))

[3. 时间戳(Timestamp)](#3. 时间戳(Timestamp))

[4. SACK(Selective ACK)](#4. SACK(Selective ACK))


一、 TCP 协议是什么

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接、具有可靠性、面向字节流传输层 通信协议,主要负责网络中两个应用程序之间传输数据

TCP 协议位于 OSI 七层协议模型的传输层,它主要负责:进程之间的数据传输,数据可靠到达,数据到达顺序正确,它不负责定位和寻找主机(网络层 IP 协议负责)。

TCP 协议的特点

(1)面向连接

TCP 在通信之前,需要先建立连接。

cpp 复制代码
客户端          服务器

   SYN  ------->
   
   <------- SYN+ACK
   
   ACK  ------->
   
      建立连接

连接建立后,双方维护通信状态,从而进行数据通信。

其中该过程被称为 TCP 三次握手,下一章节详细讲。

(2)可靠性

TCP 协议保证数据传输的可靠性,即无论底层 IP 网络如何丢包、乱序,TCP 都尽可能保证应用程序收到是数据完整、有序。

实现依靠:

序号、确认应答机制、超时重传机制、滑动窗口、拥塞控制

(3)面向字节流

TCP 不关心应用层一次发送多少数据,应用层将数据拷贝到发送端的发送缓冲区中,数据什么时候发、数据一次发多少、数据丢失怎么办,全是操作系统基于 TCP 协议来做出确定。当对端收到数据时,对端的接收缓冲区存放的全是有效数据,对端的操作系统基于 TCP 协议只负责维护接收缓冲区中连续的字节流,因此 TCP 没有消息边界,需要应用层协议自主决定。这就是 TCP 粘包而需要拆包的根本原因。

(4)全双工通信

TCP 连接建立后:双方可以同时发送数据

cpp 复制代码
客户端  ========> 服务器

客户端  <======== 服务器

两个方向独立的本质是 TCP 套接字具有发送缓冲区和接收缓冲区。

这也是为什么:TCP 关闭连接需要四次挥手。

二、 TCP 报文格式

16 位源端口号

用于表明发送端主机网络进程绑定的端口号,当对端处理完请求,给予响应时,根据 16 位源端口号将数据交付给发送请求的网络进程。

4 位首部长度和 16 位目的端口号

在网络协议栈中,我们知道对于应用层数据向下交互到网卡时,每一层协议都会对上层协议进行封装,对于网卡中的数据向上交互到应用层时,每一层协议也必须要做到解包和分用。

对于 TCP 协议是如何做到解包和分用的呢?

分用:操作系统如何将数据交给本地主机的哪一个进程?

根据 16 位目的端口号,操作系统就能找到绑定对应端口号的目标进程,将应用层数据交付给指定进程,完成分用的过程。
解包:操作系统如何将 TCP 报文完成报头和有效载荷的分离?

在 TCP 报文格式中,存在着一个字段:4 位首部长度,它表明了 TCP 报头的长度,操作系统可以根据它的大小来做到报头和有效载荷的分离。

4 位首部长度,它的取值范围是 0, 15 ,而 TCP 报头的长度抛去选项字段,最低大小也得为 20 字节。那这就不对了啊!其实不然,因为 TCP 是一种协议,协议也是一种约定,而 TCP 协议明确表明该 4 位首部长度的大小 * 4 表明 TCP 报头的长度,那么它的取值范围就是 0, 60 就可以正确的表明 TCP 报头的大小,完成报头和有效载荷的分离。

32 位序号和 32 位确认序号

前置知识:理解可靠性的本质 ---- 确认应答机制

当客户端发送的数据被服务器成功接收后,服务器会通过应答报文告知客户端已经收到哪些数据。

注:不对应答做出应答,如果对应答做出应答就会出现无限套娃的情况。那如果应答丢失了,怎么办?TCP 保证可靠性?

TCP 还有一种机制叫做:超时重传机制,它可以解决应答丢失应该如何去做?---- 下一章节讲

准确理解可靠性:

1. 客户端收到应答,可以保证服务器一定收到了客户端向服务器发送的TCP报文

2. 对于客户端没有收到应答的TCP报文,无法保证该TCP报文被服务器收到

3. TCP 并不保证每一个应答报文一定能够成功到达对端,但是 TCP 通过序号和确认序号记录数据传输状态,即使应答报文丢失,也可以通过重传机制保证数据最终可靠到达

对于服务器向客户端发送TCP报文,亦是如此。

上述传输TCP报文的过程,并不是真实的过程(客户端发送一个数据,服务器应答后,客户端才能发送下一个数据)。

TCP报文传递的真实过程

客户端和服务器之间的 TCP 通信并不是简单的"一问一答"的串行过程,而是全双工通信,双方可以同时发送数据。由于 TCP 基于 IP 协议,而 IP 协议无法保证数据传输顺序,因此 TCP 报文到达对端主机由于网速等原因不一定先发送的先到达,可能出现乱序问题。数据乱序问题也是不可靠性的一种体现,需要得到解决。而对于客户端,它发送了如此多的TCP报文,假如某些数据没有得到确认,它如何知道哪些数据需要重新发送?对于服务器,它接受了如此多的TCP报文,它是如何做到让客户端知道哪一个TCP报文接受的呢? 在 TCP 报头格式中,存在着两个特别重要的字段:序号和确认序号,它们可以对 TCP 传输的数据进行编号,并记录双方已经接收的数据状态,它不仅能解决报文乱序的问题,还能解决部分应答报文丢失不需要重传的问题。

在下一章节中,我们将在滑动窗口中,重点介绍序号和确认序号。但我们需要先了解一下:

确认序号表示:接收方已经收到指定序号之前的所以数据,下一次希望接收的数据序号。

举例:

序号 = 100

数据长度 = 500

那么:确认序号 = 600

理解为什么要有两个序号?一个序号不可以吗?

TCP 是一种全双工通信协议,客户端和服务器之间可以同时进行数据发送。

例如:

客户端向服务器发送数据:

客户端:

序号 = 100

数据 = hello

与此同时,服务器也可能向客户端发送数据:

服务器:

序号 = 500

数据 = world

对于双方来说,都需要记录自己发送的数据编号,同时也需要知道对方发送的数据是否已经被成功接收。

因此,TCP 首部设计了两个字段:

  • 序号:表示自己发送的数据编号。
  • 确认序号:表示已经成功接收到对方的数据编号。

通过这两个字段,TCP 可以同时维护两个方向的数据传输状态。
可能有人会思考:

如果 TCP 只有一个序号字段,能不能通过这个序号同时完成数据发送和确认?

答案是不可以。

因为 TCP 通信存在两个独立的数据方向:

客户端 ---> 服务器

服务器 ---> 客户端

一个序号无法同时记录:

  • 当前发送方发送的数据编号
  • 当前发送方已经收到的数据编号

所以需要存在序号和确认序号来完成全双工的特性。

对于一些聪明的人,可能会想:如果只存在一个序号,当客户端向服务器发送TCP报文时,服务器不能知道自己收到的是应答报文还是数据报文。所以需要序号和确认序号来表明报文的类型,这样做确实可以,但没有必要,因为TCP报文是存在许多类型的,TCP报头中存在专门的标志位来表示报文的类型。

所以序号和确认序号的作用并不是区分 TCP 报文类型。

两个序号存在的根本原因是:

TCP 需要同时维护双方的数据传输状态,从而实现可靠的全双工通信。
序号和确认序号附加优势 ---- 捎带确认机制

例如:

客户端发送:

你今天晚上吃饭了吗?

服务器回复:

我今天晚上吃了,你呢?

服务器在回答客户端问题的同时,也发送了自己的数据。

对应到 TCP 中:

服务器发送的 TCP 报文中同时包含:

  • 序号:服务器发送的数据编号
  • 确认序号:对客户端数据的确认编号

也就是说,一个 TCP 报文可以同时完成:

  1. 发送自己的数据
  2. 确认收到的数据

这样可以减少额外 ACK 报文的发送,提高网络通信效率

该机制被称为捎带确认机制,对于同时包含有效序号和有效确认序号的 TCP 报文被称为捎带应答

16 位窗口大小

对于应用层调用 read/recv 系统调用获取网络数据,并不是直接从网卡中获取,而是操作系统接收到网络数据后,通过协议栈进行解包和分用,得到 TCP 报文,并将 TCP 报文中的有效载荷拷贝到内核级 TCP 接收缓冲区中。应用层调用 read/recv 时,实际获取的是接收缓冲区中的数据。

但是,计算机内存资源是有限的,因此 TCP 接收缓冲区也存在大小限制。

假设客户端向服务器请求资源,服务器不断向客户端发送数据。如果服务器发送速度为 100MB/s,而客户端应用程序处理速度只有 10MB/s,那么大量数据会不断堆积在客户端 TCP 接收缓冲区中。

当接收缓冲区空间不足时,如果发送方仍然继续发送数据,接收方对其进行丢弃,就会导致大量无效的数据传输和资源浪费。虽然 TCP 可以通过重传机制保证数据最终可靠到达,但是这种方式会严重降低网络通信效率

因此,TCP 协议引入了流量控制机制,通过 TCP 报头中的 16 位窗口大小字段解决上述问题。
16 位窗口大小:表示接收方当前允许发送方发送的数据量,即 TCP 接收缓冲区中剩余空间的大小。

在 TCP 通信过程中,接收方会通过 TCP 报文中的窗口大小字段,将自己当前接收缓冲区剩余可用空间告知发送方。

由于 TCP 是面向连接的协议,在建立连接时需要进行三次握手,双方会交换初始窗口大小信息。

在数据传输过程中,接收方也会持续通过应答报文携带窗口大小,动态调整发送方的数据发送速度。

该机制称为:流量控制机制

(关于窗口大小、滑动窗口以及发送窗口和接收窗口之间的关系,将在后续章节详细介绍。)

标志位

TCP 首部中存在多个标志位字段,用于表示当前 TCP 报文的类型以及 TCP 当前所处的状态

标志位的本质就是比特位,当比特位为 1 时,表明它是有效的,否则是无效的。

前面我们介绍了:

  • 序号:表示发送数据的位置
  • 确认序号:表示已经接收的数据
  • 窗口大小:表示接收方当前能够接收的数据量

但是 TCP 通信过程中还存在一些特殊情况:

例如:

  • 如何表示我要建立连接?
  • 如何表示我要关闭连接?
  • 如何表示这个报文是确认报文?
  • 如何表示连接出现异常?

这些信息无法通过序号、确认序号等字段表示,因此 TCP 在报头中设计了多个标志位,用于描述当前 TCP 报文的作用。

常见标志位如下:

标志位 含义
SYN 建立连接
ACK 确认应答
FIN 关闭连接
RST 重置连接
PSH 提醒接收方尽快交付数据
URG 紧急数据标志

其中:

SYN、ACK、FIN 是 TCP 中最重要的三个标志位。

ACK 标志位

ACK(Acknowledgment)表示:

当前 TCP 报文中的确认序号字段是否有效。

例如:

ACK = 1

ack = 1000

表示:

确认序号有效,已经收到对方 1000 之前的数据。

TCP 通信过程中,大部分数据报文都会携带 ACK。

例如:

客户端:

序号 = 100

data = hello

服务器:

ACK = 1

ack = 105

表示:

hello 已经收到,希望下一次从105开始发送。

SYN 标志位

SYN(Synchronize)表示:

请求建立 TCP 连接。

TCP 建立连接时:

客户端发送:

SYN = 1

表示:

我要和你建立连接。

服务器收到后回复:

SYN = 1

ACK = 1

表示:

我同意建立连接,并确认收到你的请求。

(三次握手会详细介绍)

FIN 标志位

FIN(Finish)表示:

请求关闭 TCP 连接。

例如:

客户端发送:

FIN = 1

表示:

我的数据发送完毕,希望关闭连接。

服务器收到后:

ACK = 1

确认收到关闭请求。

之后双方分别关闭自己的发送方向。

(四次挥手详细介绍)

RST 标志位

RST(Reset)表示:

重置 TCP 连接。

当 TCP 连接出现异常情况,例如:

  • 连接不存在
  • 端口没有监听
  • 收到无法处理的数据

TCP 可以发送 RST 报文,直接终止连接。

例如:

客户端连接一个不存在的端口:

connect()

服务器返回:

RST = 1

表示:

该连接不存在。

PSH 标志位

PSH(Push)表示:

希望接收方立即将数据交付给应用层。

正常情况下:

TCP 数据到达后,会先存放在接收缓冲区。

当 PSH 标志被设置:

TCP 会提示接收方:

这部分数据应该尽快交给应用程序。

不过现代操作系统中,PSH 的实际影响并不像早期网络中那么明显。

URG 标志位

URG(Urgent)表示:

当前 TCP 报文中包含紧急数据。

当 URG=1 时:

紧急指针字段有效。

用于告诉接收方:

有部分数据需要优先处理。

不过现在实际应用非常少。

16 位紧急指针

TCP 首部中存在一个 16 位紧急指针字段 ,它用于表示 TCP 报文中紧急数据的位置

但是需要注意:

紧急指针字段只有在 TCP 首部中的 URG 标志位被设置为 1 时才有效。

为什么需要紧急指针?

正常情况下,TCP 是按照字节流顺序传输数据的:

例如:

A B C D E F G

接收方会按照:

A → B → C → D → E → F → G

顺序读取数据。

但是在某些特殊情况下,发送方希望告诉接收方:

某一部分数据需要优先处理。

例如:

用户正在通过远程连接操作服务器:

普通输入:

ls

cd /home

紧急输入:

Ctrl+C

此时 Ctrl+C 需要被优先处理,不能等待前面的普通数据全部处理完成。

因此 TCP 提供了紧急数据机制。
紧急指针如何工作?

假设:

发送的数据:

A B C D E F

其中:

D

是紧急数据。

TCP 报文:

URG = 1

Urgent Pointer = 3

表示:

从当前序号开始偏移 3 个字节的位置存在紧急数据。

接收方根据紧急指针找到紧急数据位置,并优先处理。

16 位紧急指针:表示紧急数据在 TCP 字节流中的偏移位置,只有当 URG 标志位被设置为 1 时才有效。该字段占 16 位,可以表示较大的偏移范围,用于帮助接收方快速定位紧急数据。

在 Linux 等现代操作系统中,TCP 紧急数据通常按照 1 字节的带外数据(OOB Data)进行处理,但 TCP 协议本身没有规定紧急数据的大小。应用层可以利用这 1 字节数据自行定义不同的含义,例如通过约定不同数值表示不同的紧急事件,从而实现简单的优先级通知机制。
为什么现在很少使用?

虽然 TCP 协议设计了紧急数据机制,但是在现代网络编程中使用非常少。

原因:

  1. 应用层通常自己设计优先级机制。

例如:

  • 消息类型字段
  • 优先级字段
  • 控制消息
  1. TCP 紧急数据机制不同操作系统实现存在差异。

因此实际开发中:

更多使用:

应用层协议 + 消息优先级设计

来实现类似功能。

16位校验和

它的主要作用是用于检测 TCP 报文在传输过程中是否发生了数据损坏

补充:TCP 校验和相关理解

发送端会根据 TCP 首部、数据以及相关的 IP 信息计算出一个16位检验和值,接收端收到报文后,会按照同样的规则重新计算校验和,然后进行比较。如果计算结果不一致,说明报文在传输过程中很可能发送了数据损坏,接收端就将该 TCP 报文直接丢弃。

选项字段

TCP 首部中存在一个可变长度的选项字段,用于扩展 TCP 的功能。

TCP 首部最小长度为:

20 字节

但是 TCP 在设计时考虑到未来可能需要增加新的功能,因此预留了选项字段。

由于选项字段长度不固定,因此 TCP 首部中设计了:

数据偏移(首部长度)字段,用于表示 TCP 首部实际长度。

例如:

没有选项:

TCP首部 = 20字节

存在选项:

TCP首部 > 20字节

接收方通过数据偏移字段知道 TCP 数据从哪里开始。

常见 TCP 选项

1. MSS(Maximum Segment Size)

最大报文段长度。

表示:TCP 通信双方能够接收的最大 TCP 数据长度。

例如:

客户端发送 SYN:

MSS = 1460

表示:我希望你发送给我的 TCP 数据部分最大为 1460 字节。

MSS 可以避免 TCP 分段过大。

2. 窗口扩大选项(Window Scale)

前面讲窗口大小时提到:

TCP 首部窗口字段只有 16 位:

最大65535字节

但是现代网络带宽很高,65535 字节可能不够。

因此 TCP 通过窗口扩大选项扩展窗口大小。

3. 时间戳(Timestamp)

用于:

  • 计算 RTT(往返时间)
  • 防止旧数据包影响新连接
4. SACK(Selective ACK)

选择确认。

默认 TCP ACK 是累计确认:

例如:

收到:

1 2 3 5

缺少:

4

只能告诉发送方:

ack=4

SACK 可以告诉发送方:

1~3 收到

5 收到

4缺失

减少不必要的重传。

TCP 选项字段用于扩展 TCP 功能,使 TCP 协议能够适应不同网络环境和需求。由于选项字段长度不固定,因此 TCP 通过数据偏移字段确定 TCP 首部长度。常见选项包括 MSS、窗口扩大、时间戳以及 SACK 等。

相关推荐
风景的人生7 分钟前
Linux虚拟机网络故障排查与解决方案
运维·网络·ssh
..Dauntless..22 分钟前
【Linux】权限问题——拥有者、所属组和其他用户的协调
linux·运维·服务器
Allen_LVyingbo24 分钟前
2026医疗AI编程:医院信息工程部规模化编程与代码审核路径(上)
网络·人工智能·cnn·transformer·知识图谱·ai编程
平行云35 分钟前
实时云渲染信创架构解析:从GPU池化到全栈适配的技术演进
linux·unity·docker·ue5·webgl·数字孪生·实时云渲染
handler0135 分钟前
【Linux】信号:内核的“敲门声”
linux·运维·服务器·c++·c·信号·signal
T1mzhou37 分钟前
ARM64 Linux 6.10内核启动流程6-psci.c和ATF/U-Boot 的电源接口
linux·服务器·c语言
147API40 分钟前
学生模型和教师模型怎样做路由,阈值应该看哪些指标
网络·人工智能·深度学习·机器学习·智能路由器
Elastic 中国社区官方博客1 小时前
隐藏在可观测性数据中的安全攻击
大数据·网络·安全·elasticsearch·搜索引擎·全文检索
ch3nyuyu1 小时前
驱动第三阶段
linux
见闻小天地1 小时前
空调机房冷冻泵与冷却泵是什么?选型要点与赛莱默方案解析
大数据·网络