UCB CS168 笔记 Chapter 3 Transport
本文最后更新于 2026年4月17日 晚上
Transport Layer Design Principles
网络层的缺陷:
- IP是尽力而为的,数据包可能出现一系列问题 (dropped, corrupted, reordered, delayed, duplicated)
- 让程序员在编码层面考虑数据包的拆分和合并过于复杂
从而,传输层的目标:
- 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达成一致:

- 第一次握手:
SYN消息,包含Client的ISN(意味着Client到Server的字节流将从这个ISN开始计数); - 第二次握手:
SYN-ACK消息,包含Server的ISN(意义和第一次握手同理),同时包含ack,代表成功接收Client的ISN; - 第三次握手:
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可以设置,我们之前讲了四个:
SYNACKFINRST