TCP 协议简析

基础知识

作为网络基础知识,相信大家对 TCP/IP 5层协议模型 不会感到陌生:

  • 应用层(应用层,表示层,会话层):具体的应用逻辑
  • 传输层:提供进程间的通信服务
  • 网络层:提供端系统之间的数据透明传送
  • 链路层:数据在具体物理设备上的表示(设备驱动、网卡)
  • 物理层:具体物理设备之间的链接(电缆)

其中的 TCP 与 IP 协议是互联网的基石。

IP 协议

IP Internet Protocol 协议解决了大规模、异构网络的互联互通问题:

  1. 将不同物理设备的帧转换为统一的 IP 数据报 datagram,实现异构物理设备间的通信
  2. 为每个设备分配一个唯一的逻辑地址,屏蔽物理地址的差异,实现异构物理设备间的寻址

IP 协议提供不可靠 Unreliable、无连接 Connectionless 的数据报传输功能:

  • 不可靠:不能保证IP数据报能成功地到达目的地
IP 协议仅保证提供 尽力而为Best Effort 的输服务。

当发生某种错误时,如某个路由器暂时用完了缓冲区,IP有一个简单的错误处理算法:丢弃数据报,然后发送ICMP消息报给信源端。

  • 无连接:不维护任何关于后续数据报的状态信息
每个数据报的处理是相互独立的。

如果一信源向相同的信宿发送两个连续的数据报(先是A,然后是B),每个数据报都是独立地进行路由选择,可能选择不同的路线,因此B可能在A到达之前先到达。

受限于协议的特性,IP 数据报可能出现 丢包乱序。因此基于IP协议的互联网络必然是一个不可靠的网络。

TCP 协议

TCP Transport Control Protocol 协议是一个面向连接的、可靠的、基于字节流的传输层协议。其主要目的是在不可靠的互联网络上提供可靠的端到端字节流传输。

互联网络的不同部分可能有截然不同的拓扑结构、带宽、延迟、数据包大小和其他参数。
TCP的设计目标是能够动态地适应互联网络的这些特性,而且具备面对各种故障时的健壮性。

其核心的功能点可以概括为两方面:

  • 流量控制:保证数据传输效率

    • 数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组
    • 滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出
  • 可靠传输:保证数据传输正确性

    • 到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认
    • 超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片
    • 失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层
    • 重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据
    • 数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发

TCP 细节

报文结构

TCP 的基本传输单位是报文段 segment,其结构如下:

  • 端口号是 16bit 的无符号数,每个TCP段都包含源端和目的端的端口号,用于寻找发端和收端应用进程。
    这两个值加上 IP首部中的源端IP地址和目的端IP地址唯一确定一个TCP连接。

  • 序号是 32bit 的无符号数,用来标识由发送端发出的数据字节流,它表示在这个报文段中的的第一个数据字节。 TCP用序号对每个字节进行计数。

  • 确认序号也是 32bit 的无符号数,用于标识发送确认的一端所期望收到的下一个序号。确认序号应当是上次已成功收到数据字节序号加1。

  • 首部长度给出首部中32bit字的数目。需要这个值是因为任选字段的长度是可变的。然而,没有任选字段,正常的长度是20字节。这个字段占4bit,因此TCP最多有60字节的首部。

  • 特殊标记位。它们中的多个可同时被设置为1。

    • URG 紧急指针
    • ACK 确认序号有效
    • PSH 接收方应该尽快将这个报文段交给应用层
    • RST 重建连接
    • SYN 同步序号用来发起一个连接
    • FIN 发端完成发送任务
  • 窗口大小是一个16bit字段,其单位为字节,因而窗口大小最大为65535字节。发送端可以通过这个字段来声明接受窗口大小,告知对方自己期望接受的数据量,从而实现流量控制。

  • 选项部分是用于支持 TCP 的一些扩展功能:数据分片、窗口放大、时间戳

数据分片与重组

路径MTU

目前最常见的局域网协是以太网Ethernet,其网络拓扑为总线型拓扑,即多个计算机共享同个信道。计算机要发送信息时,会通过共享线路将信息广播到其他计算机上。当多个计算机同时发送消息时,彼此之间会有干扰,导致数据发送失败。

因此以太网引入了一个冲突检测collision detection的功能,保证通过时刻只有一个计算机在网络上发送消息。

这造成了两个问题:

  • 每次发送都要执行冲突检测,需要额外的通信开销(数据封装、设备资源)。如果每次只传输一个字节,传输性能会十分低下,因此应该尽可能减少传输次数,即一次尽可能传输更多的数据。

  • 信道是共享的,同一时刻只能有一个计算机在发送消息。如果每次都传输完整的数据,那么其他计算机会等待很久才能响应,这对于一些实时通信任务来说是个噩梦。

以太网定义其基本的通信单位是数据帧 frame,其最基本的格式如下:

为了在效率与公平上达到一个平衡,以太网定义每个数据帧的最大为 1518。除去固定 14 字节的头部与 4 字节的尾部,数据负载的长度最多为 1500 字节。这个 1500 就是我们常说的以太网的最大传输单元 —— MTU

[lhop@localhost ~]$ netstat -i
Kernel Interface table
Iface       MTU Met    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0       1500   0   947894      0      0      0   597731      0      0      0 BMRU
lo        65536   0        0      0      0      0        0      0      0      0 LRU

MTU 是由底层网络的特性所决定的,如果两台主机之间的通信要通过多个网络,那么每个网络的链路层就可能有不同的 MTU。通信路径中的最小 MTU,决定了整个通信链路的 MTU 上限,它被称作 路径MTU

IP 分片

IP包头中用2个字节描述 IP 报文总长度,因此IP数据包的最大长度是64K字节(65535)。如果IP层有一个数据报要传,而且数据的长度比链路层的MTU还大,那么IP层就需要进行分片fragmentation,把数据报分成若干片并保证每一片都小于MTU。

IP层接收到一份要发送的IP数据报时,它要判断向本地哪个接口发送数据(选路),并查询该接口获得其MTU。IP把MTU与数据报长度进行比较,如果需要则进行分片。

数据分片 可以发生在原始发送端主机上,也可以发生在中间路由器上。数据重组 由目的端的IP层来完成,其目的是使分片和重新组装过程对传输层 (TCP和UDP) 是透明的

当IP数据报被分片后,每一片都成为一个分组,具有自己的IP首部,并在选择路由时与其他分组独立。这样,当数据报的这些片到达目的端时有可能会失序,但是在IP首部中有足够的信息让接收端能正确组装这些数据报片。

尽管IP分片过程看起来是透明的,但有一点让人不想使用它:即使只丢失一片数据也要重传整个数据报。其原因如下:

如果对数据报分片的是中间路由器,而不是起始端系统,那么起始端系统就无法知道数据报是如何被分片的,无法只重传数据报中的一个数据报片。

传输层分片

当在以太网上传输数据时,TCP 协议在建立连接时,会根据以太网的 MTU 与 IP 层的头部长度来协商一个最大报文段长度 MSS = MTU - IP头部长度 = 1472。

  • 发送端向 IP 层发送报文前,会按照 MSS 分割成多个报文段,然后逐个向 IP 层发送。
  • 接收端从 IP 层接收到报文后,会将分片进行重组。

整个过程在传输层完成,避免了 IP 层的分片处理。

当使用 UDP 协议时,用户发送的报文会被原封不动的发送给 IP 层。


UDP包头内有总长度字段,同样为两个字节,因此UDP数据包的总长度被限制为 65535 从而保证这样恰好可以放进一个IP包内,简化了 UDP/IP 的实现。

当 IP 层需要发送大于 MTU 的数据报时,IP 层会对数据进行分片—重组处理。如果 UDP 数据报较大,并且网络状况较差的话,频繁的重传会造成传输效率的低下。

因此,使用 UDP 进行数据传输时,发送端最好将 UDP 数据报的大小控制在 MSS 以内。

到达确认与超时重传

前面说过,TCP 提供一种面向连接的、可靠的字节流服务。而 IP 层本身不存在连接的概念,因此TCP 需要在进程间维护一个传输字节流的连接。此外,为了保证消息不会产生乱序,TCP 会为在连接中传输的每个字节分配一个唯一的序列号,用于保证字节直接的顺序不会被打乱。

三次握手

建立连接是由 Client 向 Server 发起的:

  1. Client 会给 Server 发送一个 SYN
  2. Server 每收到一个新的 SYN 包都会创建一个半连接,然后向 Client 发送 SYNACK
  3. Client 在收到 Server 的 SYN/ACK 包后会发出 ACK,Server 收到该 ACK 后,三次握手就完成并产生了一个 TCP 全连接

在建立连接时,双方会在 SYN 报文中协商一个初始序号 ISN

  • 发送端每次发送新数据时,会递增这个序号seq,表示该报文段首字节的字节流编号。
  • 接受方接收到报文后,会返回一个确认序号ack,表明这个序号之前的数据已经收到。

SYN 报文的特殊之处在于:

虽然其不附带有字节数据,但是其本身需要占用一个字节序号,因此 ACK 包中返回的确认序号需要在 ISN 的基础上递增一个字节。

四次挥手

断开连接可以由 Client 或 Server 任意一端发起,也可以双方同时发起:

  • 主动关闭:主动向对端发送 FIN 包关闭链接,然后会接收 ACK
  • 被动关闭:接收到对端的 FIN 包后再发送 FIN 包,也会向对端回 ACK

主动关闭方维持 TIME_WAIT 状态的意义:

最后发送的这个 ACK 包可能会被丢弃掉或者有延迟,这样对端就会再次发送 FIN 包。如果不维持 TIME_WAIT 这个状态,那么再次收到对端的 FIN 包后,本端就会回一个 Reset 包,这可能会产生一些异常

超时重传

前面提到,TCP 数据报可能会在传输过程中丢失:

  • 发送方发送的分组丢失
  • 接收方响应确认的分组丢失

上面两种丢失情况,对于发送方来说结果是相同的:接收不到确认分组。因此 TCP 引入了超时重传机制来解决报文丢失的问题:

发送端每发送一个报文段,TCP会为其保留一个副本并设定一个计时器并等待确认信息。如果计时器超时,而发送的报文段中的数据仍未得到确认,则重传这一报文段,直到发送成功为止。

影响超时重传机制协议效率的一个关键参数是重传超时时间 RTO:

  • RTO 过大将会使发送端经过较长时间的等待才能发现报文段丢失,降低了连接数据传输的吞吐量
  • RTO 过小可能将一些延迟大的报文段误认为是丢失,造成不必要的重传,浪费了网络资源

TCP协议采用自适应算法记录数据包的往返时延 RRT,并根据往返时延设定 RTO 的取值:

RTT = 传播时延 + 排队时延(路由器和交换机)+数据处理时延(应用程序)

一般来说,RTO 的取值会略大于 RTT 以保证数据包的正常传输。

拥塞窗口与接收窗口

拥塞窗口

在实际的应用中,一个 TCP 连接传输数据时不可能独占网络,因此网络的可用带宽是动态变化的。为了最大化的利用网络带宽,TCP 实现了一个动态调整传输速率的拥塞控制机制:

  • 网络空闲时提升传输速度,提高数据传输效率
  • 网络拥塞时降低传输速度,避免丢包触发不必要的超时重传

该机制的核心是发送端的拥塞窗口

在发送端设置一个大小为 cwnd 发送窗口结构,cwnd 根据网络的拥塞情况(TCP 重传率)动态变化,发送端只能发送小于或等于拥塞窗口大小的数据。

拥塞控制是 TCP 协议的核心,并且对传输性能有着至关重要的影响。其中一个著名的拥塞算法是 TCP Reno,大致可将其分为 4 个阶段:

  • 慢启动
TCP 连接建立好后,发送方就进入慢速启动阶段。
初始时 cwnd 为 1,然后逐渐地增大发包数量。这个阶段每经过一个 RTT,发包数量就会翻倍,直到 cwnd 增大到一个阈值 ssthreshold(该阶段的 cwnd 以指数形式增长)
  • 拥塞避免
当 cwnd 增大到 ssthreshold 后 TCP 就进入了拥塞避免阶段。
在这个阶段 cwnd 不再成倍增加,而是一个 RTT 增加 1,即缓慢地增加 cwnd,以防止网络出现拥塞(该阶段的 cwnd 线性增长)
  • 快速重传 / 超时重传
随着传输速率的不断上升,丢包是难以避免的,针对不同的丢包场景,TCP 引入了两种重传策略:
  • 超时重传
    发送端超过 RTO 后仍然收不到 ack 响应,会据此判断此时网络已经出现了拥塞:

    1. ssthreshold = cwnd / 2
    2. cwnd = 1
    3. 进入慢启动阶段
  • 快速重传
    发送端连续收到 3 个重复的 ack 序号后,会据此判断数据包出现了丢失,但此时网络未出现拥塞情况(拥塞窗口不必恢复到初始值):

    1. cwnd = cwnd / 2
    2. ssthreshold = cwnd
    3. 进入快速恢复阶段
  • 快速恢复
快速重传阶段检测到报文丢失后,会进入快速恢复阶段,立即重传丢失的数据段而无需等待 RTO 超时:
  • cwnd = cwnd + 3 * MSS
  • 重传数据
  • 再每收到一个重复ACK,cwnd = cwnd + 1
  • 直到收到不重复ACK,设置cwnd = ssthreshold,进入拥塞避免阶段

接收窗口

除了网络状况外,发送方还需要知道接收方的处理能力。如果接收方的处理能力差,那么发送方就必须要减缓它的发包速度。接收方的处理能力是通过接收窗口 rwnd 来表示的:

  • 接收方在收到数据包后,会给发送方回一个 ack,并后把自己的 rwnd 大小写入到 TCP 头部的 win 这个字段,这样发送方就能根据这个字段来知道接收方的 rwnd
  • 发送方在发送下一个 TCP 数据报的时候,会先对比发送方的 cwnd 和接收方的 rwnd,得出这二者之间的较小值,然后控制发送的数据量不能超过这个较小值

rwnd 的最大值由最大接收缓冲区空间确定,并且用户可以动态指定每个 TCP 连接的接收缓冲大小。然而 TCP 头部中的 win 这个字段只有 16bit,能够表示的大小最大只有 65535(64K)。为了支持更大的接收窗口rwnd以满足高性能网络,TCP 提供了窗口缩放选项 WSopt 来突破这一限制。

此外,针对一些 RTT 较大的高带宽网络,提高 rwnd 有助于提升传输效率,为此 TCP 引入了能够根据网络状况自动调整接收缓存的算法,这种自动调整技术也称为 DRS (Dynamic Right-Sizing)




参考资料:

posted @ 2020-10-04 16:55  buttercup  阅读(660)  评论(0编辑  收藏  举报