传输层
考试大纲
- 传输层的功能
- 复用与分解
- 传输层寻址与端口号、无连接服务与面向连接服务
- 流量控制与拥塞控制
- 可靠数据传输、停止—等待协议、滑动窗口协议(后退 N 步协议-GBN、 选择重传协议-SR)、协议信道利用率
- UDP 数据报、UDP 校验
- TCP 协议特点、TCP 段结构、TCP 连接管理、TCP 可靠传输、TCP 流量控制与拥塞控制
# 1. 概述与传输层服务
传输层协议为运行在不同主机上的进程提供了一种逻辑(而非物理)通信机制,在端系统中实现。
- 发送方:将应用递交的消息分成一个或多个的报文段,并向下传给网络层。
- 接收方:将接收到的报文段组装成消息, 并向上交给应用层。
传输层的数据分组称为报文段(segment)。
网络路由器仅作用于数据报的网络层字段,即他们不检查传输层报文段的字段。
可以使用多种运输层协议,因特网提供了 TCP 和 UDP 两种协议。
传输层 vs. 网络层
网络层:提供主机之间的逻辑通信机制;
传输层:提供应用进程之间的逻辑通信机制。
UDP(用户数据报协议):“尽力而为”的不可靠、无连接的服务;
TCP(传输控制协议):可靠的、面向连接的服务。
均不保证延迟和带宽。
我们将传输层分组称为报文段,但有些因特网文献称 TCP 分组为报文段,UDP 分组为数据报。我们统称都为报文段,以免与网络层的分组“数据报”混淆。
# 2. 多路复用和多路分解
这项技术的目的:将由网络层提供的主机到主机交付服务延伸到为运行在主机上的应用程序提供进程到进程的交付服务。
如果某层的一个协议对应直接上层的多个协议/实体,则需要多路复用/分用。
在接收主机中的运输层实际上并没有直接将数据交付给进程,而是将数据交给了一个中间套接字。任一时刻不只有一个套接字。每个套接字都有唯一的标识符:
- UDP Socket 标识符:(dst-ip,dst-port)
- TCP Socket 标识符:(src-ip,src-port,dst-ip,dst-port)
将传输层报文段中的数据交付给正确的套接字的工作称为多路分解;
在源主机从不同套接字中收集数据块,并为每个数据块封装上首部信息从而生成报文段,然后将报文段传递到网络层,所有这些工作称为多路复用。
端口号:16 比特的数,范围 0 ~ 65535,0 ~ 1023 是周知端口号。
# 2.1 无连接的多路复用与多路分解
一个 UDP 由一个(dst-ip,dst-port)标识。如果两个 UDP 报文段有着不同的源 ip 或源端口号,但具有相同的目的 ip 和目的端口,那么这两个报文将通过相同的套接字被定向到相同的目的进程。
但这不是说接收方就无法获得源 ip 和源端口号了,只是不把他作为标识而已。
# 2.2 面向连接的多路复用与多路分解
一个 TCP 套接字由 (src-ip,src-port,dst-ip,dst-port)标识。
- 服务器可能同时支持多个TCP Socket;
- Web服务器为每个客户端开不同的 Socket
# 3. UDP
- “Best effort”服务,UDP段可能丢失或非按序到达。
- 无连接,UDP发送方和接收方之间不需要握手,每个UDP段的处理独立于其他段
UDP 为什么存在
- 无需建立连接 (减少延迟)
- 实现简单:无需维护连接状态
- 头部开销少
- 没有拥塞控制: 应用可更好地控制发送时间和速度
使用 UDP 的应用是可能实现可靠传输的,这可以通过在应用程序自身中建立可靠机制来完成。
# 3.1 UDP 报文段结构
- UDP 首部只有四个字段,每个字段由两个字节组成,共 8 个字段。
- 长度字段指示了在 UDP 报文段中的字节数(首部 + 数据)
# 3.2 UDP 检验和
UDP 为什么提供了检验和?
虽然许多链路层协议也提供了差错检测,但不能保证所有链路都提供了差错检测。此外,即使报文段经链路正确地传输,当报文段存储在某台路由器的内存中时,也可能引入比特差错。在既无法确保逐链路的可靠性,又无法确保内存中的差错检测的情况下,如果端到端数据传输服务要提供差错检测,UDP 就必须在端到端的基础上在运输层提供差错检测,这是一个在系统设计中被称颂的端到端原则的例子。
端到端原则:因为某种功能必须基于端到端实现,在与较高级别提供这些功能的代价相比,在较低级别上设置的功能可能是冗余的或几乎没有价值的。
UDP 检验和提供了差错检测功能,用于确定当 UDP 报文段从源到达目的地移动时,其中的比特是否发生了改变。但他对差错恢复无能为力。
发送方:将段的内容视为16-bit整数。校验和计算:计算所有整数的和 ,进位加在和的后面而不是丢弃(称为回卷),将得到的值按位求反,得到校验和。发送方将校验和放入校验和字段。
接收方:计算所收到段的校验和,将其与校验和字段进行对比:不相等则检测出错误,相等则没有检测出错误(但可能有错误)。
校验和计算示例
# 4. 可靠数据传输的原理 ★
什么是可靠:不错、不乱、不丢。
约定:
rdt/udt: 可靠/不可靠数据
rdt_send():上层调用(如app),传输可靠数据
udt_send():由rdt调用,通过不可靠信道传输数据给receiver
deliver_data():rdt调用,传递数据给上层
rdt_rcv():当packets抵达信道的rcv端时调用
渐进地设计可靠数据传输协议的发送方和接收方,只考虑单向数据传输,但控制信息双向流动。
# 4.1 构造可靠数据传输协议
# 4.1.1 经完全可靠信道的可靠数据传输:rdt 1.0
rdt1.0假设了完全可靠信道的可靠数据传输。
rdt1.0
- sender:接收高层数据,打包后通过信道传输
- receiver:从底层接收packet,从中取数数据后传给较高层
- 横线上面是事件,下面是动作。
- 这种理想情况下,receiver 无需提供任何信息给sender,因为无需担心出错
# 4.1.2 经具有比特差错信道的可靠数据传输:rdt 2.0
rdt2.0 针对有bit差错信道的可靠数据传输。
- 如何从错误中恢复?
- ACK: 接收方显式地告知发送方分组已正确接收
- NAK:接收方显式地告知发送方分组有错误
- 发送方收到 NAK 后,重传分组
- 基于这种重传机制的rdt协议称为ARQ(Automatic Repeat reQuest)协议
rdt2.0
- sender:接收上层调用后把数据和checksum打包通过信道发送-->进入wait state:如果收到NAK,则重发packet继续当前state;如果收到ACK-->进入等待命令的下一状态
- receiver:收到packet后,如果pkt中有bit错误,返回NAK;如果没有错误,提取数据,传递数据,返回ACK
注意,当发送方处于等待 ACK 或 NAK 的状态时,它不能从上层获得更多的数据;这就是说,rdt_send()
事件不可能出现;仅当接收到 ACK 并离开该状态时才能发生这样的事件。因此发送方将不会发送一块新的数据,除非发送方确信接收方已正确接收当前分组。由于这种行为,rdt2.0 这样的协议被称为停等协议。
Rdt 2.0中引入的新机制:
- 差错检测
- 接收方反馈控制消息: ACK/NAK
- 重传
# 4.1.3 rdt 2.1
rdt 2.0 缺陷:ACK/NAK本身出现错误。
可能的解决和新的问题:简单的方法是给Receiver返回的ACK/NAK也设置checksum.当Sender收到含糊的ACK/NAK分组,则重传pkt。困难在于 receiver 不知道上一次 ACK/NAK 是否被 sender 正确接收,从而无法区分自己正在接收的 pkt 是上次重传还是新的。
实际方案:在pkt中添加序号,即把当前pkt的序号放在字段中,结合停等机制,sender会重发不确定的pkt,而receiver通过序号就知道接受的是重发pkt还是全新pkt。
sender:
rdt2.1 sender
等待调用0状态 --> 打包发送pkt0 --> 停等0状态:
- 当收到NAK或发现ACK/NAK出错(corrupt),则重新发送pkt(ACK/NAK本身无需携带序号,由于停等机制,sender知道这次ACK/NAK出错发生在最近的pkt0),继续停等0状态
- 当收到ACK且反馈本身无误(notcorrupt)-->进入等待调用1状态
receiver:
rdt2.1 receiver
等待传入0状态:
- 如果发现传递出错(corrupt),则打包返回NAK,继续等待传入0状态;(错误分组)
- 如果接收到pkt1,传递无误,则返回ACK,继续等待传入0状态(失序分组)
- 如果发现pkt0,传递无误-->提取数据,传递数据,返回ACK(正常分组)-->进入等待传入1状态
注:
- sender端的corrupt表示ACK/NAK混淆,receiver端的corrupt表示pkt有误;
- receiver 在等待 pkt0 时如果接收到 pkt1,表示这是一个失序的以前的分组,要回复一个 ACK。如果回复了 NAK,则 sender 会继续发送该分组从而陷入死循环。
rdt2.1 VS. rdt2.0
sender:
- 为每个分组增加了序列号;
- 两个序列号(0, 1)就够用,是因为采用了停等协议;
- 需校验ACK/NAK消息是否发生错误
- 状态数量翻倍,状态必须“记住”“当前”的分组序列号
receiver:
- 需判断分组是否是重复。当前所处状态提供了期望收到分组的序列号,如果重复则丢弃。
- 注意:接收方无法知道 ACK/NAK 是否被发送方正确收到
# 4.1.4 rdt2.2(可视为不使用 NAK 的 rdt2.1)
rdt2.2 是在有比特差错的信道上实现的一个无 NAK 的可靠数据传输协议。
分析:rdt2.1 使用了从接收方到发送方的肯定确认和否定确认。当接收到失序的分组时,接收方对所接收的分组发送一个肯定确认。如果接收到受损的分组,则接收方将发送一个否定确认。如果不发送 NAK,而是对上一次正确接收的分组发送一个 ACK,我们也能实现与 NAK 一样的效果。
FSM:
rdt2.2
Sender:等待调用0状态 --> 打包发送pkt0 --> 停等0状态:
- ① 当收到ACK1或ACK/NAK混淆时,重发pkt0,继续停等0状态
- ② 当收到ACK0且反馈本身无误(notcorrupt) --> 进入等待调用1状态
Receiver:等待传入0状态:
- ① 收到pkt1或传递出错,发送ACK1
- ② 收到pkt0且传递无误 --> 提取数据,传递数据,返回ACK0 --> 进入等待传入1
冗余ACK体现在:进入①前,Sender必定收到了ACK1,再次进入①收到ACK1,说明Receiver没有正确接收pkt0
# 4.1.5 rdt3.0
rdt3.0即比特交替协议(alternating-bit protocol)实现了有bit差错和丢包的可靠数据传输。
问题:如果信道既可能发生错误,也可能丢失分组,怎么办?“校验和 + 序列号 + ACK + 重传”够用吗?
解决方法:发送方等待“合理”时间:
- 如果没收到ACK,重传;
- 如果分组或ACK只是延迟而不是丢了,重传会产生重复,序列号机制能够处理,接收方需在ACK中显式告知所确认的分组;
- 需要定时器。
rdt3.0
Sender:基本类似rdt2.2版本。等待调用0状态 -->打包发送pkt0,开始计时 --> 停等0状态:
- ① 收到ACK1或ACK/NAK混淆,触发计数器进入(计时一直进行)
- ② 时间到,重发pkt0,重新计时
- ③ 收到ACK0且反馈本身无误,停止计时-->进入等待调用1状态
Receiver:等待传入0状态:
- ① 收到pkt1或传递出错,返回ACK1,保持等待0
- ②收到pkt0且传递无误,返回ACK0 --> 进入等待传入1状态
总结rdt3.0的四种运行,分组号总在 01 间交替,因此叫比特交替协议。
rdt3.0 运行示例
无丢包操作 | 分组丢失 | 丢失ACK | 过早超时 |
---|---|---|---|
归纳一下数据传输协议的要点,在检验和、序号、定时器、肯定和否定确认分组这些技术中,每种机制都在协议的运行中起到了必不可少的作用。
# 4.2 信道利用率分析
rdt3.0 信道利用过低,以下面的例子来证明。
考虑 A 与 B 通信,两端的往返传播时延 RTT = 30ms,彼此通过一条发送速率 R = 1 Gbps 的信道相连,分组长 L = 1000 byte = 8000 bit,发送一个分组进入链路的所需时间是:
在停等协议下,sender 从 t = 0 开始发送分组,
发送方信道的利用率:发送方实际忙于将发送比特送进信道的那部分时间与发送时间之比,发送发利用率为:
# 4.3 流水线可靠数据传输
这是对 rdt3.0 停等协议的一种改进。rdt3.0中信道的利用率过低,一个简单解决办法是:不以停等方式运行,允许发送发发送多个分组而无需等待确认。许多从发送方向的分组可视为一条流水线,故这种技术被称为流水线技术。
- 必须增加序号范围。每个输送中的分组必须有一个唯一的序号。
- 协议的 sender 和 receiver 两端不得不缓存搓个分组。sender 至少应能缓冲已发送但还未确认的分组,receiver 或许也需要缓存那些已正确接收的分组。
- 所需序号范围和对缓冲的要求取决于协议如何处理丢失、损坏以及延时过大的分组。解决流水线的差错恢复有两种方法:回退 N 步(GBN)协议和选择重传(SR)协议。
# 4.3.1 回退 N 步协议(GBN 协议)
GMN 协议允许发送方发送多个分组(当有多个分组可用时)而不需要等待确认,但也受限于在流水线中未确认的分组数不能超过某个最大允许数 N。
- 基序号(base):最早未确认分组的序号;
- 下一个序号(nextseqnum):最小的未使用的序号(即下一个待发分组的序号);
- N:窗口长度,即流水线中未确认的分组数不能超过最大允许数 N。
- [0, base - 1] 段内的序号:已经发送并被确认的分组
- [base, nextseqnum - 1] 段内的序号:已经发送但未被确认的分组
- [nextseqnum, base + N - 1] 段内的序号:能用于那些要立即被发送的分组
施加 N 的窗口大小的限制原因是:1. 流量控制 2. 拥塞控制
在实践中,如果分组的序号字段的比特数为 k,则序号范围是
在 TCP 中有一个 32 bit 的序号字段,其中 TCP 序号是按字节流中的字节来进行计数的,而不是按分组计数。
sender FSM:
GBN sender
- 初始base=(next)seq=1,此后等待上层的调用,只要seq<base+N即buffer未满,就可接收数据打包发送,然后seq++;(如果发送数据时 base == seq,表示没有已发送但未被确认的分组,此时定时器要重启)
- 如果收到正确ACK,对序号为 n 的分组的确认采用累计确认的方式,表明接收方已正确接收到序号 n 及以前的所有分组。当base==seq说明buffer里没有pkt等待确认,停止计时,否则重启计时;
- 收到错误回复时什么都不用做;
- 当时间到发生超时事件,重发当前窗口从[base]~[seq-1]的所有pkt
所以 GBN Sender 一共响应三种类型的事件:上层的调用、收到一个 ACK、超时事件。
receiver FSM:
GBN receiver
- 初始化(expect)seq=1;
- 接收到正确且合序的pkt,提取数据,并将分组数据交付到上层,返回对应序号的ACK,seq++;
- 其余默认情况下丢弃收到的分组并返回当前最大正确顺序seq的ACK;
GBN 特点:
- 顺序性:对于sender,必须收到base的ACK才能窗口向右滑动,否则do nothing;对于receiver,必须接收expectseq的pkt才能返回相应序号的ACK并seq++
- 按窗口重发:当timeout时,无论处于sender的 [base, seq-1] 的pkt是否能正确到达,重发所有(回退)
- 累计确认:对序号为n的分组确认时,表明接收方对n和n之前所有的pkt都正确接收
# 4.3.2 选择重传(SR)
GBN 本身存在的性能问题:单个分组的差错就能够引起 GBN 重传大量分组,许多分组根本没必要重传。随着信道差错率的增加,流水线可能会被这些不必要重传的分组所充斥。
选择重传协议通过让发送方仅重传那些它怀疑在接收方出错(丢失或受损)的分组而避免了不必要的重传。再次用 N 来限制流水线中未完成、未被确认的分组个数。
SR
Sender 的事件与动作:
- 从上层收到数据。当从上层接收到数据后,SR 发送方检查下一个可用于该分组的序号。如果序号位于发送方的窗口内,则将数据打包并发送;否则就像在 GBN 中一样,要么将数据缓存,要么将其返回给上层以便以后传输。
- 超时。定时器再次被用来防止丢失分组。然而,现在每个分组必须拥有其自己的逻辑定时器,因为超时发生后只能发送一个分组。可以使用单个硬件定时器模拟多个逻辑定时器的操作。
- 收到 ACK。如果收到 ACK ,倘若该分组序号在窗口内,则SR发送方将那个被确认的分组标记为已接收。 如果该分组的序号等于 send_base,则窗口基序号向前移动到具有最小序号的未确认分组处。如果窗口移动了并且有序号落在窗口内的未发送分组,则发送这些分组。
Receiver 的事件与动作:
- 序号在 [rcv_base, rcv_base+ N - 1] 内的分组被正确接收。在此情况下,收到的分组落在接收方的窗口内,一个选择 ACK 被回送给发送方。如果该分组以前没收到过,则缓存该分组。如果该分组的序号等于接收窗口 的基序号(rcv_base),则该分组以及以前缓存的序号连续的(起始于rcv_base的)分组交付给上层。 然后,接收窗口按向前移动分组的编号向上交付这些分组。举例子来说如下图,当收到一个序号为 rcv_base=2 的分组时,该分组及分组3、4、5可被交付给上层。
- 序号在 [rcv_base - N, rcv_base - 1] 内的分组被正确收到。在此情况下,必须产生一个ACK,即使该分组是接收方以前已确认过的分组。
- 其他情况。忽略该分组。
- SR 接收方将确认一个正确接收的分组而不管其是否按序。失序的分组将被缓存直到所有的丢失分组(即序号更小的分组)皆被收到为止,这时才可以将一批分组按序交付给上层。所以 SR 未采用累积确认机制。
- SR 协议为每个分组设置了一个计时器。
Notice
Notice 1:接收方重新确认(而不是忽略)已收到过的那些序号小于当前窗口基序号的分组
例如,给定在图3-23中所示的发送方和接收方的序号空间,如果分组send_base的ACK没有从接收方传播回发送方,则发送方最终将重传分组send_base,即使显然(对我们而不是对发送方来说)接收方已经收到了该分组。如果接收方不确认该分组,则发送方窗口将永远不能向前滑动!这个例子说明了SR协议(和很多其他协议一样)的一个重要方面。对于哪些分组已经被正确接收,哪些没有,发送方和接收方并不总是能看到相同的结果。对SR协议 而言,这就意味着发送方和接收方的窗口并不总是一致。
Notice
Notice 2:窗口长度必须小于或等于序号空间大小的一半
当我们面对有限序号范围的现实时,发送方和接收方窗口间缺乏同步会产生严重的后果。考虑下面例子中可能发生的情况,该例有包括4个分组序号0、1、2、3的有限序号范围且窗口长度为 3。假定发送了分组0至2,并在接收方被正确接收且确认了。此时,接收方窗口落在第4、5、6个分组上,其序号分别为3、0、1。现在考虑两种情况:
- 在第一种情况下,如下图 a 所示,对前3个分组的ACK丢失,因此发送方重传这些分组。因此,接收方下一步要接收序号为0的分组,即第一个发送分组的副本。
- 在第二种情况下,如下图 b 所示,对前3个分组的ACK都被正确交付。因此发送方向前移动窗口并发送第4、5、6个分组,其序号分别为3、0、1。序号为3的分组丢失,但序号为0的分组到达(一个包含新数据的分组)。
例题
-
解:这道题先分析b更合适,注意在GBN下:
b).接收方expseq=k,说明从pkt[k-N]~pkt[k-1]都已经确认接收返回ACK,则取值范围[k-N,k-1]
a).由b),接收方返回了N个ACK,但不能保证都正确抵达:
- 最坏:N个ACK都出问题,sender的base还是k-N,则seq取值范围[k-N,k-1]
- 最好:N个ACK都正确接收,窗口已经移动N位,base=k,此时seq的变化范围[k,k+N-1]
SR接收方窗口大小问题--分组序号有限:0,1,2,3且receiver窗口大小为3. a)receiver的三个ACK全部丢失,因此sender需要重传第一次的seq为012的pkt012
b)sender发送pkt3丢失,产生不同步,sender发送pkt4(seq0).receiver需要确认seq3和第二次出现的seq0==>对于receiver来说,无法辨别这时的seq0是新pkt或者重传
解:结合上一题,依旧设此时expseq=k,窗口大小N
考虑极端情况下,s和r"最不同步"的情况即receiver接受了N个pkt,返回的N个ACK全部丢失.此时receiver窗口最右端为k+N-1;sender的seq最坏为k-N,此时最大不同步距离为
包含2N个不同序列号,则为了避免发生序号重叠,应该保证序号空间范围k≥2N
# 5. TCP
# 5.1 TCP 连接
TCP连接是逻辑练级,只在端系统运行,中间的网络元素不会维持 TCP 的连接状态。 TCP 是全双工、点对点的。
建立连接的过程被称为"三次握手"。
客户进程通过 socket 传递数据流,数据一旦通过这个门,就由 TCP 控制了。TCP 将这些数据引导到发送缓存中,接下来 TCP 时不时地从发送缓存中取出一块数据传递给网络层。
- TCP 规范并没有提及应何时实际发送缓存中的数据给网络层;
- TCP 从缓存中取出的报文段中的数据量受限于最大报文段长度(MSS)。MSS 通常根据最大链路层长度(即最大传输单元 MTU)来设置。设置 MSS 要保证一个 TCP 报文段加上 TCP/IP 首部长度(通常 40 byte)将适合单个链路层帧。以太网和PPP都有 1500 byte 的 MTU,所以 MSS 典型值为 1460。
注意 MSS 是指报文段中应用层数据的最大长度,而不是包括首部的 TCP 报文段的最大长度。
TCP 为每块客户数据配上一个 TCP 首部,从而形成多个 TCP 报文段。 连接的每一段都有各自的发送缓存和接收缓存。
# 5.2 TCP 报文段结构
TCP 报文段由首部字段和一个数据字段组成。
- Source port、Dest port:实现多路复用和多路分解;
- seq num(32 bit)和 ack num(32 bit):实现可靠数据传输;
- recv window:用于流量控制,指示接收方愿意接受的字节数量;
- header length:TCP 首部长度可变,通常该字段为空,典型长度是 20 byte;
- options:协商 MSS;窗口调节因子;时间戳选项;
- flat field(6 bit):ACK 比特指示确认字段中的值是有效的;RST、SYN、FIN 用于连接的建立和拆除。
# 5.2.1 序号和确认号
TCP 把数据看成一个无结构的、有序的字节流,一个报文段的序号是该报文段首字节的字节流编号。
比如一个包含 50 0000 字节的文件,MSS = 1000 字节,那么有:
- 这里我们假设初始序号为 0,但实际上一条 TCP 连接的双方均可以随机地选择初始序号。这样做可以减少将那些仍在网络中存在的来自两台主机之间先前已终止的连接的报文段,误认为是后来这两台主机之间新建连接所产生的的有效报文段的可能性。
主机 A 填充进报文段的确认号是主机 A 期望从主机 B 收到的下一个字节的序号。
例子:假设主机A已收到一个来自主机B的包含字节0~535的报文段,以及另一个包含字节900~1000的报文段。由于某种原因,主机A还没有收到字节536~899的报 文段。在这个例子中,主机A为了重新构建主机B的数据流,仍在等待字节536(和其后的字节)。因此,A到B的下一个报文段将在确认号字段中包含536因为TCP只确认该流中至第一 个丢失字节为止的字节,所以TCP被称为提供累积确认。
TCP RFC并没有为失序到达的报文段如何处理作规定任何规则,这留给编程人员去实现,可选方案:
- 接收方立即丢弃失序报文段;(更简单)
- 接收方保留失序的字节,并等待缺少的字节以填补该间隔。(效率更高,实际所采用)
Example
一个回显程序的交互过程:
第二个报文段由服务器发送给客户,有两个目的:
- 对收到的数据提供一个确认;
- 回显字符 'C'
这种确认被称为是被捎带在服务器到客户的数据报文段中的。
# 5.3 往返时间的估计与超时
TCP 采用超时/重传机制来处理报文段的丢失问题,这与 rdt 思路一致。但问题也有很多,最明显的就是超时间隔必须大于该连接的往返时间。
SampleRTT:报文段的样本 RTT,是从某报文段被发出(交给 IP)到对该报文段的确认被收到之间的时间量。
- 仅在某个时刻做一次 SampleRTT 测量;决不为已被重传的报文段计算。
EstimatedRTT:是 SampleRTT 的加权平均值,每收到一个新 SampleRTT,就会更新 EstimatedRTT:
DevRTT:RTT 的偏差,用于表征 RTT 的变化:
的推荐值为 0.25
有了上面的数据,超时间隔应该大于等于 EstimatedRTT,否则将造成不必要的重传,但也不能打太多,应给 Estimated 加一个余量:
推荐初始 TimeoutInterval 值为 1 秒,只要报文段更新 EstimatedRTT,就用上述公式再次计算 TimeoutInterval。
# 5.4 可靠数据传输
TCP 确保一个进程读出的数据流无损坏、无间隙、非冗余和按序。
- TCP 的报文段编号是第一个数据字节的字节流编号。
- 仅使用单一的重传定时器。
# 5.4.1 TCP 发送方事件
● 事件1:
将数据封装到报文段中,加编号,定时器未开启则启动定时器,时间间隔为 TimeoutInterval。
● 事件2:
重传引起超时的报文段,然后重启定时器。
● 事件3:
采用累计确认,若 seq > sendBase 则更新 sendBase = seq;若还有未确认报文段则重启定时器。
TCP 发送端程序:
NextSeqNum = InitialSeqNum
SendBase = InitialSeqNum
loop (forever) {
switch(event)
event: data received from application above
create TCP segment with sequence number NextSeqNum
if (timer currently not running)
start timer
pass segment to IP
NextSeqNum = NextSeqNum + length(data)
event: timer timeout
retransmit not-yet-acknowledged segment with
smallest sequence number
start timer
event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase = y
if (there are currently not-yet-acknowledged segments)
start timer
}
} /* end of loop forever */
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
一些有趣的情况:
情况1:A 向 B 发送一个序号为 92 大小 8 byte 的报文段,发出后 A 等待一个确认号为 100 的报文段。B 收到但回给 A 的确认报文丢失,这时超时事件发生,A 重传相同的报文段,B 再次收到后根据序号发现重复则丢弃。
情况2:A 连续发送了两个报文段
情况3:A 发送了与情况2中相同的报文,B 回复后
# 5.4.2 超时间隔加倍
定时器超时后,TCP 重传具有最小序号但还未被确认的报文段,并将下次的超时时间间隔设置为先前值的两倍,而不是用从 EstimatedRTT 和 DevRTT 推算到的值。
然而,每当定时器因另外两个事件而启动时,TimeoutInterval 都是由最近的 EstimatedRTT 和 DevRTT 推算得到。
因为定时器超时很可能是因为网络拥塞造成的,如果持续重传,反而使得拥塞更加严重。
# 5.4.3 快速重传
问题:超时重传周期可能比较长,当一个报文段丢失时,这种长超时周期会延迟发送发重传丢失的分组。
冗余 ACK:指再次确认某个报文段的 ACK,而发送方先前已经收到了对该报文段的确认。
接收方产生 ACK 的建议
事件 | TCP 接收方动作 |
---|---|
具有所期望序号的按序报文到达,所有在期望序号及以前的数据都已经确认 | 延迟的 ACK(为了看一下是否有下一个按序报文段到来)。最多等待 500ms,如果下一个没有来则回复 ACK。 |
具有所期望序号的按序报文段达到。另一个按序报文段等待 ACK 传输 | 立刻发送单个累积 ACK,以确认两个按序报文段。 |
比期望序号大的失序报文段到达。检测出间隔 | 立刻发送冗余 ACK,指示下一个期待字节的序号(其为间隔的低端的序号) |
能部分或完全填充接收数据间隔的报文 | 倘若该报文段起始于间隔的低端,则立刻发送 ACK |
如果 TCP 发送发接收到对相同数据的 3 个冗余 ACK,它把这当成一种指示,说明跟在这个已被确认过 3 次的报文段之后的报文段已经丢失。一旦收到 3 个冗余的 ACK,TCP 就执行快速重传,即在该报文段的定时器过期之前重传丢失的报文段。
# 5.4.4 TCP 的可靠传输是 GBN 还是 SR?
TCP 是一个 GBN 协议还是一个 SR 协议:
- TCP 确认是累积式的,接收失序的报文段不会被接收方逐个确认;
- TCP 仅维护 SendBase、NextSeqNum 两个变量;
- TCP 重传至多一个分组;
- 一种对 TCP 的修改意见是接收方有选择地确认失序报文段
=> TCP 的差错恢复机制是 GBN 和 SR 的混合体。
# 5.5 流量控制
如果某应用程序从接收缓冲区读取数据相对缓慢,而发送方发送的太多、太快,发送的数据就会很容易地使该连接的接收缓存溢出,因此 TCP 提供了流量控制服务来消除这种可能性。
流量控制是一个速度匹配服务,即发送方的发送速率与接收方应用程序读取速率相匹配。TCP 通过让发送方维护一个称为接收窗口的变量来提供流量控制。通俗的说,接收窗口用于给发送方一个指示——该接收方还有多少可用的缓存空间。
接收窗口用 rwnd
表示,根据缓存可用空间数量来设置:
利用 rwnd 实现流量控制的方式:
主机 B 通过把当前的 rwnd 值放入发给主机 A 的报文段接收窗口字段中,通知主机 A 它在该连接的缓存中还有多少可用空间。
主机 A 跟踪两个变量:
LastByteSent
和LastByteAcked
,两变量之差就是 A 发送出去但还未被确认的数据量,通过将该数据量控制在rwnd
以内即可保证 B 的接收缓存不会溢出。
还存在的问题:假设 B 告诉了 A 自己的 rwnd = 0,还没有什么数据要发给 A,这样待 B 清空缓存后 A 也无法知道,所以主机 A 将被阻塞而再也不能发送数据。 sol:TCP 规范要求,当 B 的 rwnd = 0 时,A 继续发送只有一个字节数据的报文段,这些报文段将会被 B 确认,最终缓存将开始清空,并且确认报文里将包含一个非 0 的 rwnd 值。
# 5.6 TCP 连接管理
# 5.6.1 三次握手
step1:client 给 server 发送一个 SYN 报文段,同时带上随机选择的初始序号 client_isn;
step2:server 分配缓存和变量,回复一个 SYNACK 报文段(SYN置1;确认号 client_isn + 1;服务器的初始序号 server_isn);
step3:client收到SYNACK,也为连接分配缓存和变量。同时发送另一个报文段给server,对server的报文段进行确认,此时连接已经建立,SYN置为 0,可以携带数据。
之后:此后可以互相发送seg,在每个seg中SYN都被置为0
# 5.6.2 四次挥手
假设 client 打算关闭连接:
- step1:client 向 server 发送
FIN
置 1 的终止报文段; - step2:server 收到 FIN,回复
ACK
,然后 - step3:server 发送自己的
FIN
置 1 的终止报文段; - step4:client 收到 FIN,回复
ACK
,server 收到 ACK,连接关闭。
实际上,client 和 server 都能主动终止该连接。当连接结束后,主机中的资源将被释放。
# 5.6.3 TCP 状态
在一个 TCP 连接的生命周期内,运行在每台主机中的 TCP 协议在各种 TCP 状态 之间变迁。
client 端:
server 端:
提示
假如当一台主机接收到一个 TCP 报文段,其端口号或源 IP 地址与该主机上进行中的套接字都不匹配,则该主机将向源发送一个特殊的重置报文段,该 TCP 报文段将 RST 置 1,它告诉该源“我没有那个报文段的套接字,请不要再给我发送该报文段了”。
# 5.7 拥塞控制原理
在实践中,丢包一般是在网络拥塞时由于路由器缓存溢出引起的,分组重传因此作为网络拥塞的前兆来对待。
非正式定义: 太多发送主机发送了太多数据或者发送速度太快 ,以至于网络无法处理。
拥塞的表现:● 分组丢失(路由器缓存溢出) ● 分组延迟过大(在路由器缓存中排队)
拥塞控制 VS. 流量控制
拥塞控制是链路上的控制;流量控制是 S/R 端的控制
# 5.7.1 拥塞的原因和代价 非重点
(1)情况 1:两个发送方和一台具有无限大缓存的路由器
- 两个 sender,两个 receiver,在容量为 R 的共享链路上传输
- 一个路由器, 无限缓存
- 没有差错恢复等
每连接的吞吐量:
- 当发送速率在 0 ~ R/2 之间时,接收方的吞吐量等于发送方的发送速率;
- 当超过 R/2 时,它的吞吐量只能达到 R/2。无论发送速率有多高,都无法超过 R/2
时延:
- 当发送速率接近 R/2 时,平均时延越来越大;
- 超过 R/2 时,路由器中的平均排队分组数就会无线增长,平均时延会变得无穷大。
因此,虽然从吞吐量角度看,运行在总吞吐量接近 R 的状态也许理想,但从时延的角度看,却远不是理想状态。
我们发现了拥塞网络的一种代价:
(2)情况 2:两个发送方和一台具有有限缓存的路由器
- 假定路由器缓存有限,因此当分组到达一个已满的缓存时会被丢弃;
- 假定每条连接都是可靠的,因此被路由器丢弃的分组终将被重传;
表示应用程序发送的初始数据量, 表示运输层向网络中发送的报文段(含初始数据和重传数据)速率。
情况 2.a:主机 A 能够确定路由器中的缓存是否空闲,因而空闲时才发送一个分组。这种情况下不会产生丢包,
情况 2.b:发送方仅当确定了一个分组已经丢失时才重传。当
情况 2.c:发送方也许会提前发生超时并重传在队列中已被推迟但还未丢失的分组。这样初始分组和重传分组都可能到达接收方,重传分组被接收方丢弃,使得路由器转发重传分组做了无用功。假设每个分组被路由器转发两次,情况如下图。这里看到了网络拥塞的另一种代价:
(3)情况 3:4个发送方和具有有限缓存的多台路由器及多跳路径
- 4 台主机发送分组,每台都要经过交叠的两跳路径传输
- 都采用超时重传机制
- 所有主机有相同的
值,所有路由器的链路容量都是 R 字节/秒
当
# 5.7.2 拥塞控制方法 非重点
这里讨论实践中所采用的两种主要拥塞控制方法。
根据网络层是否为传输层拥塞控制提供了显式帮助来分类:
- 端到端拥塞控制:网络层没有提供显式支持。即使网络存在拥塞,端系统也必须通过对网络行为的观察(如分组丢失与时延)来推断之。TCP 采用这种方法。
- 网络辅助的拥塞控制:路由器向发送方提供关于网络中拥塞状态的显式反馈信息。这种反馈可以简单地用一个比特来指示链路中的拥塞情况。最近 IP 和 TCP 也能够选择性地实现网络辅助拥塞控制。通常由两种实现方式:
- 阻塞分组形式。直接反馈信息可以由网络路由器发给发送方。
- 路由器标记或更新从发送方流向接收方的分组中的某个字段来指示拥塞的产生。
# 5.8 TCP 拥塞控制
由于 IP 层不向端系统提供显式的网络拥塞反馈,TCP 必须使用端到端拥塞控制。TCP 所采用的的方法是让每一个发送方根据所感知到的网络拥塞程度来限制其能向连接发送流量的速率。
发送方跟踪一个拥塞窗口(cwnd
)变量。在一个发送方中未被确认的数据量不会超过 cwnd 与 rwnd 的最小值。即
- cwnd:拥塞窗口
- rwnd:接收窗口
后面的讨论假设 rwnd 足够大,未被确认的数据量仅受限于 cwnd。
通过约束发送方中未被确认的数据量,间接的限制了发送方的发送速率。发送方的发送速率大概是 rate = cwnd/RTT byte/s
。通过调节 cwnd 的值,发送方因此能调整它向链接发送数据的速率。
我们将发送方的“丢包事件”定义为:要么出现超时,要么收到来自接收方的 3 个冗余 ACK。发生的丢包事件被当做出现了拥塞的指示。
TCP 使用下列指导性原则来回答问题:
- 一个丢失的报文意味着拥塞,因此当丢失报文段时应当降低 TCP 发送方的速率;
- 一个确认报文段指示该网络正在向接收方交付发送方的报文段,因此,当对先前未确认报文段的确认到达时,能够增加发送方的速率。
下面开始介绍 TCP 拥塞控制算法,包括三个主要部分:① 慢启动 ② 拥塞避免 ③ 快速恢复。①② 是强制部分,③ 是推荐部分。
# (1)慢启动 SS
- 初始:给cwnd起始设置为 1MSS(最大报文段长度)的较小值,使得初始速率为 MSS/RTT;
- 传输:之后每次 sender 确认一个 seg 后,把 cwnd 增加一个 MSS;
- 第二次,sender发出两个seg,因此会有两个ACK,sender对cwnd加两个MSS,下次同时发出四个seg...依次类推达到cwnd每次翻倍的效果
- 结束:何时结束这种指数增长呢?有三种结束慢启动增长 cwnd 的方式:
- 当发生超时时,发送方将 cwnd 置为 1 并重新开始慢启动,同时置 ssthresh 为 loss 发生前的 cwnd 的一半。
- 当 cwnd == ssthresh(慢启动阈值)时,结束慢启动并且 TCP 转移到拥塞避免模式。
- 如果检测到 3 个冗余的 ACK,这时 TCP 执行一种快速重传并进入快速恢复状态。
# (2)拥塞避免
一旦进入拥塞避免状态,cwnd 的值大约是上次遇到阻塞时的值的一半。这时每个 RTT 只将 cwnd 的值增加一个 MSS。何时应当结束拥塞避免的线性增长呢?:
- 当出现超时时,sshresh = cwnd/2,cwnd = 1;
- 当出现 3 个冗余 ACK 时,说明网络情况还可以,相比于超时指示的丢包应当没那么剧烈:将 ssthresh 置为 cwnd 的一半,将 cwnd 减半,再加上 3 个冗余ACK要加的 3 个MSS,接下来进入快速恢复阶段。综合下来就是 ssthresh = cwnd/2,cwnd = cwnd/2 + 3。
# (3)快速恢复
在快速恢复中,对于引起 TCP 进入快速恢复状态的确实报文段,对收到的每个冗余的 ACK,cwnd 值增加一个 MSS。最终,当对丢失报文段的一个 ACK 到达时,TCP 在降低 cwnd 后进入拥塞避免状态。
快速恢复非必需构件,在早期 TCP Tahoe 版本中,所有丢包事件都是无条件将 cwnd 减至 1 个MSS并进入慢启动阶段。较新的 TCP Reno 版本则综合了快速恢复。
理解好这张图
以 TCP Reno 为例,一开始处在慢启动阶段,cwnd = 1,之后每次 cwnd 翻倍,直到达到 ssthresh 进入拥塞避免阶段,开始呈线性增长,在产生 3 个冗余 ACK 事件后进入快速恢复阶段,ssthresh = cwnd/2 = 12/2 = 6,cwnd = cwnd/2 + 3 = 9, 降低了 cwnd 后再次进入拥塞避免状态。
而对于 TCP Tahoe 版本来说,没有快速恢复阶段,而是直接将 ssthresh = cwnd/2 = 6,将 cwnd 置为 1 并进入慢启动阶段。
# TCP 拥塞控制 回顾:
忽略初始的慢启动阶段和超时引起的丢包,TCP 的拥塞控制可以概括为:
- 加性增:拥塞避免阶段每个 RTT 使 cwnd + 1;
- 减性乘:3 个冗余 ACK 使得 cwnd 减半
即 AIMD:
例题
要能够解释这张图:
例题
一个TCP连接总是以1 KB的最大段长发送TCP段,发送方有足够多的数据要发 送。当拥塞窗口为16 KB时发生了超时,如果接下来的4个RTT(往返时间)时 间内的TCP段的传输都是成功的,那么当第4个RTT时间内发送的所有TCP段 都得到肯定应答时,拥塞窗口大小是多少?
解:threshold=16/2=8 KB, CongWin=1 KB, 1个RTT后, CongWin=2 KB ,2 个RTT后, CongWin=4 KB ,3个RTT后, CongWin=8 KB ,Slowstart is over; 4个RTT后, CongWin=9 KB