深入理解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 等。

相关推荐
三言老师1 小时前
K8s 集群 LocalPV 静态 PV 资源手动创建实操
linux·运维·服务器·kubernetes
SendTomo1 小时前
send.wang(私传网)P2P直连加速文件传输
网络·网络协议·webrtc·html5·p2p
一直在努力学习的菜鸟1 小时前
Rocky Linux 8.10 编译安装 PostgreSQL 17.11
linux·运维
Doraemomo1 小时前
Linux编程-进程的执行和退出
linux·运维·服务器
longxingiot2 小时前
智慧园区全域管控物联网主机应用
网络·物联网
嵌入式阿蔡2 小时前
OTA 实战四:安全启动与固件签名(Secure Boot)—— 拒绝非法固件
网络·stm32·单片机·嵌入式硬件·嵌入式实时数据库
学计算机的计算基2 小时前
TCP 传输层硬核整理:三次握手、四次挥手、拥塞控制一次讲透
java·网络·笔记·网络协议·算法
lengjingzju2 小时前
编译与调试完全指南—第5章 GDB调试详解
linux
艾伦_耶格宇2 小时前
【ELK】-5 kibana和filebeat
linux·运维·ubuntu·elk