TCP概述及报头解析
TCP 特征概述
TCP 协议在网络 OSI 参考模型中的第四层——传输层,是一个 面向连接的(connection-oriented)、可靠的(reliable)、字节流式的(byte stream) 传输协议。
- 面向连接 :在应用 TCP 协议进行通信之前,双方通常需要通过三次握手来建立 TCP 连接,连接建立后才能进行正常的数据传输。但是同时面向连接的特性给 TCP 带来了复杂的 连接管理 以及用于检测连接状态的 存活检测机制 。同时,TCP 提供全双工通信。
- 可靠性 :TCP 层需要解决来自 IP 层的四种常见传输错误问题,分别是 比特错误 (packet bit errors)、包乱序 (packet reordering)、包重复 (packet duplication)、丢包 (packet drops),TCP要提供可靠的传输,就需要有额外的机制处理这几种错误。TCP 通过使用 确认 ,重传 ,校验和 这三种基本机制来实现可靠传输。因此TCP协议具有超时与重传管理、窗口管理、流量控制、拥塞控制等功能。
- 字节流式 :应用层发送的数据会在 TCP 的发送端缓存起来,统一分段(例如一个应用层的数据包分成两个TCP包)或者打包(例如多个应用层的数据包打包成一个TCP数据包)发送,到接收端的时候接收端也是直接按照字节流将数据传递给应用层。也就是说,TCP 的数据流是没有边界的 ,这可能导致粘包。作为对比,同样是传输层的协议,UDP 并不会对应用层的数据包进行打包和分片的操作,一般一个应用层的数据包就对应一个 UDP 包。
报头解析
报头大小为 20 ~ 60 字节 ,若没有 option ,则为 20 字节。下面对图中各部分进行解析。
-
源端口和目的端口: 分别用来唯一标识主机和服务器的进程。
源端口、目的端口、以及源 IP 地址、目的 IP 地址统称为 四元组 ,它唯一的标识了一个 TCP 连接。一个 IP 地址和一个端口号组成 套接字地址 。接收端的 TCP 层根据不同的端口号来将数据包传送给应用层的不同程序,这个过程叫做 解复用 (demultiplex)。相应的发送端会把应用层不同程序的数据映射到不同的端口号,这个过程叫做 复用 (multiplex)。
-
序列号(SN): TCP 为待发送缓冲区中的数据编号,与 ARQ 协议不同,此处是为每个字节进行编号,而不是为段进行编号 。序列号范围为 ~ 。
连接时,TCP 在范围内生成一个 随机数 作为 初始序号(ISN) ,那么第一个字节的序号应为 ISN + 1 。例如,ISN = 23 ,第一个分组大小为 500,则其序号为 24 ~ 500 ,那么下一个分组就需要从 501 开始编号。当 SYN =1 时,该序列号有效。TCP 通过序列号来判断丢包,重复和失序。
-
确认号(ACK): 当 ACK 位为 1 时,该确认号有效。接收方通过使用确认号来告知发送方已收到的字节数(或下一次想收到的字节),比如,接收方收到
SN = 54
的分组,然后回复ACK = 55
的分组来告知发送方我已收到 55 之前的字节(或下一次我想收到 55 开头的字节流)。与 GBN 相同,确认号时累积的。 -
头部长度: 表示报头总大小。注意,头部长度仅占 4 bit ,范围为 0 ~ 15 ,而报头大小为 20 ~ 60 bytes,所以头部长度的单位是 4 bytes,也就是说,若头部长度为 11,则报头总大小为
11 × 4 = 44
bytes 。 -
保留: 占 6 位,这些位必须是0。为了将来定义新的用途所保留,其中 RFC3540 将保留字段中的最后一位定义为 Nonce 标志,用于处理拥塞。
-
窗口大小: 16 位,表明接收端窗口的空闲空间大小,最大的窗口大小为64Kb 。这个值通常被称为 rwnd 。注意,它也可以表示为对方必须维持的窗口大小 ,因为发送方必须服从接收方的支配。用于流量控制。
-
校验和: 发送端基于数据内容计算一个数值,接收端要与发送端数值结果完全一样,才能证明数据的有效性。接收端 checksum 校验失败的时候会直接丢掉这个数据包。CheckSum 是根据 伪头部+TCP头+TCP数据 三部分进行计算的。
伪首部只参与校验,不占空间,不参与传输 。UDP 伪头部 = 源 IP 地址 + 目的 IP 地址 + 8位协议 + 16位UDP长度 。
UDP 发送方可以选择不计算校验和,而 TCP 必须计算校验和 。UDP 伪首部目的是让UDP两次检查数据是否已经正确到达目的地:第一次,通过伪首部的IP地址检验,UDP可以确认该数据报是不是发送给本机IP地址的;第二,通过伪首部的协议字段检验,UDP可以确认IP有没有把不应该传给UDP而应该传给别的高层的数据报传给了UDP。相较于链路层的 CRC 校验,TCP/UDP 校验和提供相对较弱的差错保护。 -
紧急指针: 16位,指向优先数据的字节,在URG标志设置了时才有效。如果URG标志没有被设置,紧急域作为填充 。
-
标志位:
- CWR(Congestion Window Reduce) :拥塞窗口减少标志被发送主机设置,用来表明它接收到了设置ECE标志的TCP包,发送端通过降低发送窗口的大小来降低发送速率
- ECE(ECN Echo) :ECN 响应标志被用来在 TCP 3次握手时表明一个 TCP 端是具备 ECN 功能的,并且表明接收到的TCP包的IP头部的ECN被设置为11。ECN(Explicit Congestion Notification)是一种由网络层辅助的拥塞控制方法,用于显式通知终端拥塞的发生。参考此处
- URG(Urgent) :该标志位置位表示紧急(The urgent pointer) 标志有效。该标志位目前已经很少使用。
- ACK(Acknowledgment):取值 1 代表 ACK 字段有效,这是一个确认包,取值0则不是确认包。
- PSH(Push) :该标志置位时,一般是表示发送端缓存中已经没有待发送的数据,接收端不将该数据进行队列处理,而是尽可能快将数据转由应用处理。在处理 telnet 或 rlogin 等交互模式的连接时,该标志总是置位的。
- RST(Reset) :用于复位相应的TCP连接。通常在发生异常或者错误的时候会触发复位 TCP 连接,这可能导致数据丢失。作用如下:
- 向一个未打开的端口发送连接请求
- 应用程序主动终止一个连接
- 应用程序还没有接收缓存中的数据,连接被提前关闭
- TWA(TIME-WAIT Assassination)
- 半开连接的情况下发送数据。参考 此处
- SYN(Synchronize) :该标志仅在三次握手建立TCP连接时有效。它提示TCP连接的服务端检查序列编号.
- FIN(Finish) :带有该标志置位的数据包用来结束一个TCP会话,但对应端口仍处于开放状态 ,准备接收后续数据。
-
选项(Option) :长度不定,但长度必须以是32bits的整数倍。常见的选项包括 MSS、SACK、Timestamp 等。可用于填充 。
常用选项为:
-
最大报文传输段(Maximum Segment Size — MSS ) :MSS只出现在前两次握手中 。
MSS 是传输层 TCP 协议范畴内的概念,顾名思义,其标识TCP能够承载的最大的 应用数据 长度,因此,MSS = MTU - 20 字节TCP报头 - 20字节IP报头,那么 在以太网环境下,MSS值一般就是1500-20-20=1460字节
-
窗口扩大选项(window scaling – WSOPT ) :该选项只出现在前两次握手中 ,因此当TCP连接建立起来后,window scale就固定了。
window size
占 16 位,最大 64 kb,在网络情况很好的状态下,这么小的窗口无法满足较高的网络性能,所以需要用 WSOPT 选项扩大窗口。该选项格式如下:1
2
3// +---------+---------+---------+
// | Kind=3 |Length=3 |shift.cnt|
// +---------+---------+---------+当使用 WSOPT 选项的时候,接收窗口的实际大小则为
Window Size << shift.cnt
,其中 shift.cnt 按照协议最大只能为14 ,当接收端接收到的 shift.cnt 大于 14 的时候,则按照 14 来处理 Window Size 。WSOPT 选项最大可将原有的 16 位 Window Size 扩展到近 30 位大小(大约1GB),可以有效提升TCP允许使用的接收缓存。 -
选择确认选项(Selective Acknowledgements —SACK ) :用于重传和拥塞控制,笔者其他文章会详述。
-
时间戳选项(timestamps ) :用于 测量往返时延 (RTTM,利于掌握网络拥塞信息),防止序列号回绕 (PAWS)(参见:序列号回绕 )
-
NOP :选项部分的每种选项长度必须是4字节的倍数,不足时用NOP补充 。注意,是用来填充选项之间的空隙。
-
EOL :用来填充整个选项部分的末尾。
举例:假如 Header Length 指定的 TCP 头长为 40 bytes,其中第 29-38 bytes 为 TSOPT 选项,则可以在第 39 byte 处添加一个 EOL 选项指示选项列表结束,可以看到 EOL 并没有位于 TCP 头的结束位置的第 40 byte。对于最后一个 byte RFC793 协议规定需要以 0 来填充。这个EOL后面填充的 0 已经不属于 TCP 选项的一部分了。然而,linux 本身发送 TCP 数据包的时候并不会添加 EOL 选项,而是通过添加一个或者多个 NOP 选项来实现整个 TCP 头长的四字节对齐。
-
FOC :用于 TFO 的 Cookie 选项,参见此处
-
文章参考: