一个 50000 字节的文件,MSS = 1000 字节,数据流的首字节编号为 0。TCP 将该数据流分为 500 个报文段,给第一个报文段分配序号 0,第二个为报文段分配序号 1000,第三个是 2000,以此类推。每个序号被写入到 TCP 报文段首部的序号字段中。
一条 TCP 连接的双方均可随机地选择初始序号。
这个问题留给程序员自己解决,有两种解决方式:
大致的抽象过程:准备发送数据,首先发送方先由应用层调用运输层的提供的方法 rdt_send()
运输层传递要发送的数据,运输层数据添加自己的首部信息,接着再由运输层调用自己的 udt_send()
向网络层发送分组,通过 UDT 这种不可靠协议的不可靠信道传输给接收方,接收方的网络层收到分组,调用接收方传输层的 rdt_rcv()
向传输层传递该分组,最后接收方的运输层调用自己的 deliver_data()
向应用层传递数据。
注意:可靠数据传输协议的下层协议可能是不可靠的。
有限状态机 FSM(Finitee-State Machine, FSM),我了解的也不多,简单的画了个图,便于可以读懂书。
rdt1.0 假设:传输过程不会丢包、丢比特,假设是完全可靠的数据传输,且假定双方发送和接收的速率一样快。
可以看到,发送端和接收端的 FSM 都只有一个状态,因此迁移状态也是指向自己。
发送方发送数据和接收方接收数据的过程:
rdt_send(data)
向运输层传递数据。make_pkt(data)
将数据进行处理,得到一个 packet
,即包。udt_send(packet)
将包发送给网络层,即信道。rdt_rcv(packet)
。rdt_rcv(packet)
所对应的动作,首先调用 extract(packet, data)
进行拆包,提取发送方发送的真实数据。deliver_data(data)
将数据发送给上层,即应用层。rdt2.0 假设:传输过程中可能出现丢比特,但是注意,目前不假设丢包(即分组)。
rdt2.0 中发送方拥有两个状态,分别是“等待来着上层的调用” 以及 “等待ACK或NAK”。
重传机制,重传机制的可靠数据传输协议称为自动重传请求协议(Automatic Repeat reQuest, ARQ)。
ARQ 协议还需要另外三种协议来处理存在比特差错的情况:
发送方发送数据和接收方接收数据的过程:
发送方应用层调用运输层 rdt_send(data)
将数据传递给运输层,运输层调用 make_pkt(data, checksum)
将数据处理,得到 sndpkt
。
将分组 sndpkt
调用 udt_send(sndpkt)
通过信道(即网络层)发送给接收方。
此时,需要等待来自接收方的响应,因此无法再次发送新的信息。
接收方网络层收到分组 sndpkt,调用 corrupt(rcvpkt)
看看分组是否有错。(在接收方包的变量名改为了 rcvpkt)
没错,则响应 ACK,若错了,则响应 NAK。
发送方接收到了来自接收方的响应信息。
若是 NAK,则重传上一个分组,若是 ACK,目前暂无动作。
注意:当发送方处于等待 ACK/NAK 的状态时,发送方无法再次发送新的数据。 因此这样的协议被称为停等(stop-and-wait)协议。
rdt2.0 目前还存在的问题:响应的 ACK 和 NAK 分组也可能会受损。
如果一个 ACK 或 NAK 分组受损,发送方将无法知道接收方是否正确的接收了上一次的分组。
解决这个问题,使用一种最简单的实现方式:【rdt2.1】
注意:目前假定不丢分组,因此 ACK/NAK 分组不需要添加分组序号。
可以发现,发送方和接收方比之前多了 1 倍,那是因为需要对“分组0” 和 “分组1” 分别进行处理。
发送方接收到对同一个分组的两次 ACK(即接收到冗余 ACK)后,就知道接收方没有正确接收到跟在被确认两次的分组后的分组,间接达到 NAK 效果,因此发送方将会再次重传。
rdt2.2 是在有特差错信道上实现的一个无 NAK 的可靠数据传输协议。
rdt2.1 余 rdt2.2 的不同之处:rdt2.2 接收方必须包括由一个 ACK 报文所确认的分组序号,发送方必须检查接收到的 ACK 报文中被确认的分组序号。
ACK 分组序号:ACK0、ACK1。
假设:比特受损、信道丢包。
发送方发送以及接收方响应 ACK/NAK 状态时都可能丢包,而其中任何一方丢失分组时,自己是不知道的,另一方自然也不知道。那么解决这种方案我们使用其中一种方法,即让发送方负责检测和恢复丢包的工作。假定发送方传输一个分组,该分组或接收方响应该分组的 ACK 发生了丢失。这两种情况下,发送方都收不到应当到来的接收方的响应。
我们可以设定一个超时定时器,若接收方等待响应超过了这个时间,那么将重新发送该分组,以此来确定分组是否丢失。
发送方至少需要等待:RTT + 接收方处理一个分组所需的时间。
既然是重传,那么就可能会造成分组冗余,但我们已经通过分组序号来解决了。
各种过程图:
D过早超时,指的就是定时器设置的时间不合理,正常时间为RTT+接收方处理一个分组的时间,而D所设置的远远小于这个值,因此可能造成以及成功接收的分组会再次重新发送,但是并不影响正常运行。
特点:
窗口格式(黑白版):
base
:基序号,最早的(即最先发送的)未确认分组的序号。nextseqnum
:下一个待发分组的序号。[0, base - 1]
:已发送并确认的分组。[base, nextseqnum - 1]
:已发送,但未确认的分组。[nextseqnum, base + N - 1]
:立即要被发送的分组。发送方的滑动窗口(鲜艳版):
在 GBN 协议中,因为使用的是累积确认,所以接收方将会丢弃所有的失序分组。假定现在期望接收到分组 n n n,而分组 n + 1 n+1 n+1 却到了,如果分组 n n n 丢失,则该分组以及分组 n + 1 n+1 n+1 最终将在发送方根据 GBN 的重传规则而被重传。
优点:接收缓存简单,即接收方不需要缓存任何失序分组。
缺点:丢弃一个接收正确的分组,后续可能导致该分组的重传也可能出错或丢失,因此甚至需要更多的重传。
对于定时器,发送方只有一个定时器。
详细过程图:
特点:
和 GBN 协议不同的是 SR 接收方的窗口为 >= 1,可以乱序接收。对于定时器,发送方的给每个分组都设置了一个定时器,因此 SR 也不需要向 GBN 那样重新发送所有已发送但未确认的分组。
详细过程图:
不是我不想了解,是我了解了 RFC 6298 单一定时器后更懵了,书上也没讲,导致图 3-25 理解不够彻底…
书上说,重传 92 后,将重启定时器,第二段只需要在新的超时之前到达就行,也就是说我可以理解为“当一个定时器重传后,将重置下一个报文段的定时时间”。
我现在已经懵逼了,就先按照书上的来吧,我查阅的 RFC 6298 资料和书上的说的貌似都不是一回事,发送多个报文段所处理的方式也不一样,额…
如图一,主机A向主机B发送一个报文段,主机B发送一个确认报文段,结果丢包了,导致在超时时间之前未到达主机A,因此发送方启动超时事件,将重传丢失的那个报文段。
如图二,主机A发送了两个报文段,即一个92,一个100,主机B收到了这两个报文段,并且发送两个确认报文,分别对应 100 和 120。假设这两个确认报文在超时时间之前还未到达主机A,等超时时间一到,将重传 92,并且重新启动超时定时器,若 120 能在新超时之前到达,则不会重传 100.
如图三,主机A发送了两个报文段,即一个报文段序号是 92,一个报文段序号是 100,主机B收到后发送两个确认号,分别对应 100 和 120,可是确认号为 100 的那个在传输的过程中丢失了,但确认号为 120 的那个却抵达了,因为 TCP 采用的是累积确认,所以主机A收到了确认号为 120 的报文就表示 119 及之前的都已经被主机B确认了,因此即便 100 丢失,也不需要重传它。
TCP 重传具有最小序号的已发送还未被确认的报文段,若该报文段多次超时,则每次超时的时间都被进行翻倍。好处是当出现网络拥塞时,若发送方持续重传分组,会使拥塞更加严重,因此 TCP 使每个发送方的重传都是经过越来越长的时间间隔后进行的。
超时重传的问题之一就是超时周期可能相对较长。会造成端到端的延迟。我看可以通过冗余 ACK 来解决,一旦接收方收到了 3 个冗余 ACK,TCP 就立即执行快速重传。
快速重传:在该报文段的定时器过期之前重传丢失的报文段。
冗余 ACK 如图所示:
产生 ACK 的建议:
流量控制: 接收方控制发送方,不让发送方发送的太多或太快以至于让接收方的缓冲区溢出。
接收窗口(rwnd)和接收缓存(RcvBuffer)示意图
如何通过变量 rwnd 来提供流量控制服务?
接收方会把当前的 rwnd 放入到发送至发送方的报文段的接收窗口字段中,以此来通知接收方该连接还有多少可用空间。起初,接收方将 rwnd = RcvBuffer。
当接收方的接收缓存已满,使得 rwnd = 0。并且将 rwnd = 0 通知给发送方后,假设接收方也没有任何数据发送给发送方。此时若接收方突然有空间了,那么请问发送方应该如何知道这个信息呢?如果它不知道,那么就无法再次发送新的数据给接收方,因为上层接收方已经通知了发送方 rwnd = 0。
为了解决发送方不知道接收方有空间而造成发送方阻塞,TCP 规范中要求:
半连接: 采用两次握手建立TCP连接时,会出现半连接状态,这是因为在这种情况下,客户端发送了SYN后,并没有等到服务器端的 ACK+SYN,可能是因为丢包了或延迟了,因此导致服务器端维护着一个半连接。
至于半连接所分配的冗余空间,如果在半连接状态下客户端没有发送ACK包,服务器端会超时关闭连接并释放资源。
老数据: 当两次握手建立连接后,客户端向服务器发送数据,过程中突然断开连接,在服务器接收到这个数据之前,突然又和客户端建立了一个 TCP 连接,接着服务器收到后来到的数据,此时这个数据属于后来的那个连接,该连接会接收这个数据,导致客户端和服务器端的数据不一致。
造成老数据的主要原因是:客户端建立的两次 TCP 连接所采用的序列号一致。
三次握手建立连接的步骤:
FIN 标志位,谁要关闭连接,就发送包含 FIN = 1 的报文。
TCP 使用端到端拥塞控制。
TCP 所采用的的方法是让每一个发送方根据所感知到的网络拥塞程度来限制其能向连接发送流量的速率。
使用这种方法,有三个问题:
TCP 连接的每一端都是由一个接收缓存、一个发送缓存和几个变量(LastByteRead、rwnd等)组成。运行在发送方的 TCP 需要跟踪一个额外的变量,即拥塞窗口(congestion window),表示为 cwnd,它对一个 TCP 发送方能向网络中发送流量的速率进行了限制。
发送方传输数据的大小取决于:min(cwnd, rwnd)
如何让发送方知道该路径出现了拥塞:
确认发送方发送速率的原则:
TCP 拥塞控制算法,该算法主要包含 3 个主要部分:
当一条 TCP 连接开始时,cwnd 的值通常初始为一个 MSS 的较小值,这就使得初始发送速率大约为 MSS/RTT。
cwnd 的值以 1 个 MSS 开始,往后每次传输报文段且被确认后,cwnd 将翻倍。
如图,过程如下:
可以看到,慢启动的 cwnd 是以指数级增长的。
结束这种指数级增长的方式:
ssthresh
慢启动阀值,指的是 cwnd 可以到达的最大值。
SS 和 CA:
进入拥塞避免模式后,不再采用指数级增长 cwnd 的方式,而是每个 RTT 只将 cwnd 的值增加一个 MSS。
一种通用的做法:对于 TCP 发送方无论何时到达一个新的确认,就将 cwnd 增加 MSS/cwnd 字节。
例如:MSS = 1460,cwnd = 14600,在一个 RTT 内发送 10 个报文段。只要到达一个 ACK,则 cwnd 增加 1/10 MSS,因此等收到 10 个报文段的 ACK 后,cwnd 就增加了一个 MSS。
结束拥塞避免模式的时机:
遇到超时事件后,cwnd 下降到一半,随后进入拥塞避免模式,开始线性增长 cwnd。
当收到 3 个冗余 ACK后:
当超时事件发生后:
3 个冗余的 ACK,表示网络还有一定的传输能力。
超时之前的 3 个冗余 ACK,表示“警报”。
乘性减:丢失事件后将 cwnd 置为 1,将 cwnd / 2 作为阀值,进入慢启动阶段。
加性增:当 cwnd > 阀值,进入拥塞避免阶段。
有两条 TCP 连接共享一段传输速率为 R 的链路。假设这两条连接具有相同的 MSS 和 RTT,它们有大量数据需要发送,且没有其它 TCP 连接或 UDP 数据报。并且忽略 TCP 的慢启动阶段,以 CA 运行。
如果 TCP 要在这两条 TCP 连接之间平等共享链路带宽,那么实现的吞吐量曲线必须从 45° 射出(平等共享带宽)。
理想状态:两个的吞吐量的和等于 R。
如图,假设一开始并不公平,处于 A 点,此刻连接1的吞吐量比连接2的吞吐量要大,为了公平,它将沿着 45° 前行(两条连接都有相同增长),最终这两条连接共同消耗的带宽将超过 R,导致分组丢失。
分组丢失采取如下措施:
执行分组丢失的措施后,以为 cwnd 减半,所以此刻到达了 C 点,此刻连接1的吞吐量比连接2的吞吐量要大,一样沿着 45° 前行,一样最终丢包,继续采取措施,最终调整到了连接1的吞吐量等于连接2的吞吐量。