UCB CS168 笔记 Chapter 3 Transport

本文最后更新于 2026年4月17日 晚上

Transport Layer Design Principles

网络层的缺陷:

  1. IP是尽力而为的,数据包可能出现一系列问题 (dropped, corrupted, reordered, delayed, duplicated)
  2. 让程序员在编码层面考虑数据包的拆分和合并过于复杂

从而,传输层的目标:

  • de-multiplexing: 在网络层我们通过IP Header决定是去TCP还是UDP;在传输层我们确定到底走哪个端口
  • abstraction
  • reliability
  • congestion-control and flow-control: 防止过载

我们定义:

  • at-least-once delivery: 只需要收到一次数据包即认为成功,如果有duplicates也视作成功;
  • exactly-once delivery: 基于at-least-once delivery实现。

bytestream abstraction:

通过网络层,原本抽象的数据包,可以变成有序的bytestream,发送者和接收方(位于应用层)无需考虑数据的丢失等可靠性问题,因为它们由传输层保证。

传输层提供两种协议:

  • TCP:

    • de-multiplexing and reliability
    • bytestream
    • abstraction
  • UDP:

    • de-multiplexing only
    • datagram
    • abstraction
    • 不提供可靠性保证,应用层自行处理

    UDP 中的消息被限制为单个数据包。若应用需要发送更大消息,需自行负责消息的分割与重组。需注意,UDP 仍通过端口号实现多路分解功能。

传输层只在end host实现,不在中间设备实现。

TCP Design

我们首先考虑如何实现at-least-once delivery

Deliver a Single Packet

RTT (Round-Trip-Time):

当接收方接收到数据包时,发送ack (acknowledgment) 来确认接受。

发送方维护一个Timer,如果超时(规定时间内没收到ack),重新发送数据包,以解决丢包问题。

如果ack也丢了,也问题不大,因为我们是at-least-once delivery,不断重发即可。

理想的RTT的设置最好是接近一次往返时间,实际中测量困难。

如果bits are corrupted: 在传输层的Header加入checksum(different from the IP layer checksum),如果有一个bad checksum,什么也不做(意味着:不发送所谓的negative acknowledgment)。

考虑所有的dropped, delayed, duplicated, corrupted问题,我们发现我们这个针对一个数据包的简单协议已经能够解决。

Reliably Delivering Multiple Packets

与上一小节涉及的协议相比,我们只需要处理re-ordered的问题。

解决方案:给每个包一个序列号,就算reordered了,end-host也可以进行重排。

这里不是stop-and-wait protocol,我们并行发送很多packets,而不是每次等待下一个的ack再重发。

如果一个packet在发送过程中,我们称之为in-flight

Window

一次发送一个数据包太慢,但一次性发送所有数据包又会压垮网络。为了解决这个问题,我们规定一个W,规定每次至多有W个packets in-flight。

为了充分利用W,我们需要满足三个约束:

Filling the Pipe

为了充分利用带宽,$W = RTT \times 每秒允许发送的最大数据包数$

Flow Control

接收方可能收到乱序的数据包,但是为了满足Bytestream Abstraction,我们必须将数据包缓存在内存中直到交付。

但是缓冲区大小有限,因此Flow control ensures that the recipient's buffer does not run out of memory.

为此,我们让接收方告知发送方其缓冲区剩余多少空间。接收方缓冲区剩余的空间量被称为通告窗口 advertised window

当发送方得知通告窗口的大小时,它会相应地调整自己的窗口

Congestion Control

由于互联网是异步的,链路的带宽不太可能只被单个连接使用。

这个算法实际上相当复杂,后面会有专门的部分介绍拥塞控制算法。

Smarter Acknowledgments

三个方式:

  • 每次发送ack的时候附上所有已经确认接受的数据包序号
  • 每次发送ack的时候,先写连续序列,再写附加的一些离散值
  • 每次发送ack的时候,只发送一个数字,代表最高累积确认号,并舍弃额外列表,TCP实际上采用这种

举个例子,收到了[1, 2, 4, 5, 6],发送的ack会是#2,因为从1开始的最长连续序列到2。

Detecting Loss

等待计时器到期太慢了。设置一个k,如果在丢失的数据包之后有 K 个后续数据包被确认,我们将认为该数据包已丢失(即使计时器尚未到期)。

对于TCP使用的cumulative acks,我们检查有k次相同的ack出现,则认为丢包。

但是会有歧义,体现在比如k=3,但是我们得到了七八个重复的ack,因为丢了很多包,我们是无从得知具体丢了哪些的。

TCP Implementation

TCP Segments

之前的讨论主要依赖于数据包抽象,但是应用程序主要依赖字节流抽象,所以我们从另一个角度思考TCP协议——从字节的角度。

我们将字节写成一列,其中取**段 (segments) **,即将字节填充满一个段,来对应我们之前所说的一个数据包。

填充段:

  • 要么填充满最大段长;
  • 每次开始填充一个新段的时候,启动一个Timer,如果到期,即使还没填满,也发送这个段。
  • 上面那个Timer,不要和决定何时重传字节数据的Timer搞混了

发送段之前,在前面加一段 TCP Header。

The TCP segment, with a TCP header and IP header on top, is sometimes called a TCP/IP packet.

MSS (Maximum Segment Size) = MTU (Maximum Transmission Unit) - IP Header Size - TCP Header Size

Sequence Numbers

如何给段编号:

  • 首先有一个 ISN (Initial Sequence Number)
  • 每次TCP连接,ISN是随机加密生成的
  • 并将第一个字节标记为 ISN+1,下一个字节标记为 ISN+2,再下一个字节标记为 ISN+3,依此类推
  • 以第一个字节的序列号作为包的代表号
  • 发送回去的Ack,是这个段的结尾的下一个字节的编号(可以理解为左闭右开区间)
  • 更准确地说,ack表示:我已接收到所有小于此编号的字节

由于 TCP 需要存储状态信息,每个字节流被称为连接或会话,因此 TCP 是一种面向连接的协议。与第三层(网络层)中每个数据包可独立处理不同,TCP 要求双方在发送数据前建立连接并初始化状态。TCP 还需要一种机制来拆除连接,以释放两端主机为状态分配的内存。

TCP is Full Duplex

TCP的一个很重要的性质:两个终端主机是双向发送信息的(回忆我们之前建立的模型,一直是一方发送一方接受)。

每个数据包同时包含发送的数据和ack。

TCP Handshake

前面说了,TCP需要建立连接和拆除连接的过程。

当我们创建一个新连接时,需要双方就两个起始 ISN(每个方向一个)达成一致。

执行三次握手,以就两个方向的ISN达成一致:

  1. 第一次握手:SYN消息,包含Client的ISN(意味着Client到Server的字节流将从这个ISN开始计数);
  2. 第二次握手:SYN-ACK消息,包含Server的ISN(意义和第一次握手同理),同时包含ack,代表成功接收Client的ISN;
  3. 第三次握手:ACK消息,Client确认接收到了Server的ISN。

三次握手之后可以互相发送消息。

End Connections

两种方式:

正常方式

  • 发送FIN,代表发送方不再发送消息,但是可以继续接受
  • 收到FIN之后发送ACK只是确认收到了消息
  • 双方都这样之后,连接终止。

异常方式

为了单方面结束连接,发送RST数据包,表示:不再发送和接收任何数据

RST 数据包通常在主机遇到错误且无法继续发送或接收数据包时使用。请注意,如果发生 RST 且终端主机崩溃并丢失其状态,则任何传输中的数据都将丢失。

如果对方继续发送数据,则反复发送RST

一个简化的TCP状态图:

Piggybacking

捎带确认

当接收方收到一个数据包时,如果它没有数据要发送,接收方有两种选择:

  • 立刻发送ack,不附带任何数据;
  • 等到有数据要发送时,再将ack和新数据一起发送 - Piggybacking

数据到底会不会Piggybacking,取决于很多因素:

  • TCP是运行在OS之上的,与应用层分离,不知道应用何时有信息要发送;
  • 而应用层运行与Bytestream Abstraction,更不可能考虑Piggybacking;
  • 再考虑OS的调度机制,实现这个也很复杂
  • 因此我们经常不会去Piggybacking。

数据总是被捎带发送的一个例子是握手过程中的 SYN-ACK 数据包。和上面讨论的问题就没关系了,因为三次握手是由OS执行的。

Sliding Window

  • 在之前的基于数据包抽象的TCP讨论中,我们认为window是任意时刻in-flight的数据包最大数,这里可以是不连续的
  • 现在,我们规定window是任何给定时刻可以传输的最大连续字节数。
  • 窗口左侧:第一个未被确认的字节
  • 窗口右侧,左侧+W

即使中间的某些字节被ack了,但是我们永远无法发出超出窗口范围的字节,我们能够发送更多字节的唯一方式是窗口向右滑动,即当确认号增加时(窗口左侧的字节被确认)。

W的大小:

  • flow control && congestion control
  • 最后是二者的最小值。

Detecting Loss and Re-sending Data

满足以下两个条件之一:

  • Timer is up(发送的数据,在一定时间之内,未收到相应的ack)
    • 在基于字节抽象的TCP中,我们认为只有一个唯一的Timer,对应窗口左侧。如果超时,则重新发送最左侧的段
    • 每次新的ack到达,Timer重置
  • 收到k次重复的ack

TCP Header

一些解释:

  • advertised window用于flow control and congestion control
  • Flags有8个bit可以设置,我们之前讲了四个:SYN ACK FIN RST