3.1 概述和运输层服务
- 运输层协议是在端系统中而不是在路由器中实现。
- 因特网有两种协议:TCP和UDP。
3.1.1运输层和网络层的关系
- 网络层提供了主机之间的逻辑通信。网络层协议叫IP,即网际协议。IP的服务模型为
尽力而为交付服务(best-effort delivery service)
,尽最大努力在通信的主机之间交付报文段,但是不做任何确保。不确保报文段的交付,不保证报文段的按序交付,不保证报文段中数据的完整性,被称为不可靠服务,分组被称为数据报(datagram)。 - 运输层协议为运行在不同主机上的应用进程之间提供了逻辑通信。运输层协议包括TCP和UDP,它们都能实现数据交付和差错检测
- 数据交付和差错检测是UDP所能提供的仅有的两种服务,与IP一样是一种不可靠服务,其分组在因特网文献中也被称作数据报(datagram),本书中和TCP分组一样称为报文段(segment)。
- TCP为应用程序提供附加服务:可靠数据传输和拥塞控制。
3.1.2因特网运输层概述
- 应用层和运输层之间:应用程序开发者必须指定使用两种运输层协议(TCP和UDP)的一种。
- 运输层和网络层之间:将主机之间交付扩展到进程之间交付被称为运输层的
多路复用(transport-layer multiplexing)
和多路分解(demultiplexing)
。
3.2 多路复用与多路分解
- 在接收端,运输层检查这些字段,标识出接收套接字,进而将报文段定向到该套接字。将运输层报文中的数据交付到正确的套接字的工作被称为多路分解(demultiplexing)。
- 在源主机从不同的套接字中收集数据块,并为每个数据块封装上首部信息从而生成报文段,然后将报文段传递到网络层,所有这些工作称为多路复用(multiplexing)。
运输层的工作:在主机上的每个套接字能够分配一个端口号,当报文段到达主机时,运输层检查报文段中的目的端口号,并将其定向到响应的套接字,然后报文段中的数据通过套接字进入所连接的进程。
运输层报文段中的特殊字段为源端口号字段和目的端口号字段。
端口号是一个16bit的数,其大小在0~65535之间。0~1023范围的端口号称为周知端口号(well-known port number),是受限制的,即它们保留给了诸如HTTP(端口号80)和FTP(端口号21)之后的周知应用层协议来使用。
1.无连接的多路复用与多路分解(Python)
clientSocket = socket(AF_INET, SOCK_DGRAM);
创建一个UDP套接字,运输层自动地为该套接字分配一个范围在1024~65535内的一个端口号,该端口号是当前未被该主机任何其他UDP端口使用的号。
clientSocket.bind(('',19157))
可通过bind()方法为这个UDP套接字关联一个特定的端口号。
一般客户端让运输层自动的分配端口号,而服务器端则分配一个特定的端口号。
一个UDP套接字是由一个二元组全面标识的,该二元组包含一个目的IP地址和一个目的端口号。源端口号用来作为“返回地址”的一部分。
2.面向连接的多路复用和多路分解(Python)
TCP的套接字是由一个四元组来标识的,与UDP不同的是,两个不同源IP地址或源端口号的到达的TCP字段将被定向到两个不同的套接字。
- TCP服务器应用程序有一个“欢迎套接字”,举例它在12000号端口上等待来自TCP客户端的连接建立请求。
serverSocket = socket(AF_INET, SOCK_DGRAM) serverSocket.bind(('',12000)) serverSocket.listen(1)
- TCP客户使用下面的代码创建一个套接字并发送一个连接建立请求报文。
clientSocket = socket(AF_INET, SOCK_STREAM) clientSocket.connect((serverName, 12000))
- 一个连接建立请求不过是一个目的端口号是12000,TCP首部的特定“连接建立位”置位的TCP报文段。这个报文段也包含一个由客户选择的源端口号。
- 当运行服务器进程的计算机主机操作系统接收到具有目的端口号12000的入连接请求报文后,就定位服务器进程,向端口正在端口号12000等待接受连接,该服务器进程则创建一个新的套接字
connectionSocket, addr = serverSocket.accept()
- 该服务器的运输层还注意到连接请求报文中的下列4个值该报文段中源端口号、源主机IP地址、目的端口号、自身的IP地址。新创建的套接字通过这4个值来标识。
所有后续到达的报文段,如果它们的源端口号、源主机IP地址、目的端口号和目的IP地址都与这4个值匹配,则被分解到这个套接字。
3.Web服务器与TCP
一台高性能的Web服务器通常只使用一个进程,为每个新的客户连接创建一个具有新连接套接字的新线程,在给定的时间内都有可能有许多连接套接字连接到相同的进程。
3.3 无连接运输:UDP
许多应用更适合UDP,原因如下:
- 关于发送什么数据以及何时发送的应用层控制更加精细。不会被拥塞控制机制所打扰。对于一些实时应用来说更适合UDP
- 无需建立连接,不需要三次握手,因此DNS运行在UDP上。
- 无连接状态,不维护追踪这些状态参数,一些专门用于特定应用的服务器能够支持更多活跃的用户。
- 分组首部开销小。
网络管理数据(SNMP)(通常在网络重压下运行)、DNS、一些多媒体应用(实时,超过某个延迟就没有意义了)都用UDP而不是TCP。
3.3.1 UDP报文段结构
由源端口号、目的端口号、长度(首部+数据)、校验和和应用数据等组成。
3.3.2 UDP校验和
- 发送方的UDP对数据段中的所有16比特字的和进行反码运算。求和时遇到的任何溢出都要被回卷。最后得到的结果放在UDP报文段的校验和字段。
- 接收方检验时,把所有数据段中的16比特字相加,再与校验和相加,如果结果16位全是1则验证是正确的,反之有差错。但是UDP报文对差错恢复无能为力。
UDP提供差错检测的必要性:
- 不能保证所有链路都提供差错检测。
- 报文段存储在某台路由器的内存中时也会引入比特差错。
3.4 可靠数据传输原理
在应用层看来,数据通过一条可靠信道实现可靠数据传输。
事实上,可靠数据信道实现为运输层(TCP)通过可靠数据传输机制实现,将数据传递给不可靠数据传输的网络层(IP)。
其中rdt代表可靠数据传输,udt代表不可数据传输,send为发送,rcv为接收。
3.4.1 构造可靠数据传输协议
1.经完全可靠信道的可靠数据传输:rdt1.0
这里假设底层信道是完全可靠的,运输层只需完成基本的发送和接收功能即可。
2.经具有比特差错信道的可靠数据传输:rdt2.0
- 对于接收方:
- 出现差错了返回NAK。
- 没差错了返回ACK。同时将分组传递给应用层。
- 对于发送方(发送完要等待响应报文):
- 响应报文为ACK则发送下一条报文。
- 响应报文为NAK则重传。
如果响应报文(即ACK或NAK分组发生差错),发送方仍需重传,且接收方无法得知发送来的数据为重传的数据还是新数据,引入了冗余分组。->rdt2.1(对ACK增加序号)
- 对于发送方:
- 发送0时:
- 如果收到ACK且ACK分组无差错,则等待上层调用发送下一个分组1。
- 否则(即收到NAK或者ACK/NAK分组出现差错),则重传分组0。
- 发送1时:
- 如果收到ACK且ACK分组无差错,则将数据传递给应用层然后等待上层调用发送下一个分组0。
- 否则(即收到NAK或者ACK/NAK分组出现差错),则重传分组1。
- 发送0时:
- 对于接收方:
- 等待分组0时
- 如果分组出现差错时,返回NAK。继续等待下层的0。
- 如果分组没有差错但是收到了分组1,则返回ACK,但是不将该分组传递给应用层,继续等待下层的0。
- 否则(分组无差错且收到了分组0),则将分组传递给应用层且返回ACK然后等待来自下层的分组1。
- 等待分组1时
- 如果分组出现差错时,返回NAK。继续等待下层的1。
- 如果分组没有差错但是收到了分组0,则返回ACK,但是不将该分组传递给应用层,继续等待下层的1。
- 否则(分组无差错且收到了分组1),则将分组传递给应用层且返回ACK然后等待来自下层的分组0。
- 等待分组0时
不用NAK,将NAK和ACK+序号合并成ACK+序号的形式。->rdt2.2(rdt2.1的简化版本)
- 对于发送方:
- 发送0时:
- 如果收到(ACK,0)且ACK分组无差错,则等待上层调用发送下一个分组1。
- 否则(即收到(ACK,1)或者ACK分组出现差错),则重传分组0。
- 发送1时:
- 如果收到(ACK,1)且ACK分组无差错,则将数据传递给应用层然后等待上层调用发送下一个分组0。
- 否则(即收到(ACK,0)或者ACK分组出现差错),则重传分组1。
- 发送0时:
- 对于接收方:
- 等待分组0时
- 如果分组出现差错时或者收到了分组1,返回(ACK,1)。继续等待下层的0。
- 否则(分组无差错且收到了分组0),则将分组传递给应用层且返回ACK然后等待来自下层的分组1。
- 等待分组1时
- 如果分组出现差错时或者收到了分组0,返回(ACK,0)。继续等待下层的1。
- 否则(分组无差错且收到了分组1),则将分组传递给应用层且返回ACK然后等待来自下层的分组0。
- 等待分组0时
3.具有比特差错的丢包信道的可靠数据传输:rdt3.0
我们让发送方负责检测和恢复丢包工作,无论是数据分组是发送方发送的分组还是接收方发送的响应分组,其丢失发送方都收不到来自接收方的响应。发送方可以等待足够长的时间来确认分组已经丢失。
将该时间确定为一个具体的时间值,超时可能是丢失也可能没有丢失,没有丢失则会引入冗余数据分组,但rdt2.2已经可以解决冗余分组。
因此实现上需要一个倒计数定时器(countdown timer),在一个给定的时间量过期之后,可中断发送方。因此发送需要能做到:每次发送一个分组,便启动一个定时器、响应定时器、终止定时器。
接收方与rdt2.2一样。
因为分组序号在0和1之间交替,因此rdt3.0有时也被称为比特交替协议(alternating-bit protocol)。
3.4.2 流水线可靠数据传输协议
rdt3.0是一个功能正确的协议,但是rdt3.0的性能核心问题在于它是一个停等协议。
假设两个端系统之间的光速往返时延RTT大约为30ms,端系统的发送速率为1Gbps,分组的长度L为1000字节。则将一个分组全部推到链路上需要时间
t
t
r
a
n
s
=
L
R
=
8000
b
i
t
1
0
9
b
i
t
/
s
=
8
μ
s
t_{trans} = \frac{L}{R} = \frac{8000bit}{10^9bit/s}=8\mu s
ttrans=RL=109bit/s8000bit=8μs
我们假设ACK分组很小,发送方发发送一个分组到达接收方,并返回一个ACK需要用时
t
=
R
T
T
+
L
/
R
t=RTT+L/R
t=RTT+L/R
则发送方的利用率为
U
s
e
n
d
e
r
=
L
/
R
R
T
T
+
L
/
R
=
0.00027
U_{sender} = \frac{L/R}{RTT+L/R}=0.00027
Usender=RTT+L/RL/R=0.00027
即发送方只有万分之2.7的时间是忙的。
解决方法为以不停等的方式运行,允许发送方一次发送多个分组而无需等待确认。
如图一次发送3个分组,即发送方利用率就基本上提高三倍。
这种技术叫做流水线。流水线技术对于可靠数据传输协议可能带来如下影响:
- 必须增加序号范围,每个传输中的分组都要有一个唯一的序列号。
- 发送方和接收方不得不缓存多个分组,发送方缓存那些已发送但没有确认的分组,接收方缓存那些正确接收的分组。
解决方式有两种:回退N步(Go-Back-N, GBN)
和选择重传(Selective Repeat, SR)
。
3.4.3 回退N步
受限流水线中未确认的分组数不能超过某个最大允许数N。
定义发送方序号标号如下:
- 基序号(base):最早未确认分组的序号。
- 下一个序号(nextseqnum):最小的未使用序号(即下一个待发分组序号)。
对于发送方的序号范围:
- [0, base-1]已经发送并确认的分组
- [base, nextseqnum-1]已经发送但未确认的分组
- [nextseqnum, base+N-1]能用于要被立即发送的分组
对于发送方:
- 上层的调用,发送分组前首先看是否超过了N的窗口长度。
- 收到一个ACK,这里确认为
累积确认(cumulative acknowledgment)
的方式,即序号为n的分组的确认表明接收方收到序号n的以前包括序号n的分组。 - 超时事件,这里的计时器只用了一个,一开始当作发送的最早的已发送但是未被确认的分组用的定时器,后来随着接收到确认分组,如果还有未确认的就重置该定时器定时剩下的未确认分组,如果都确认了就关掉定时器。定时器用于恢复数据或确认分组的丢失。
对于接收方:
- 为保持接收的分组保持有序,对于所有失序的分组全部丢弃。
3.4.4 选择重传
回退N步,会导致重传很多不必要的分组,丢失/出错1个分组,会导致剩下N-1个全部丢弃,然后重传整整N个分组。如果N很大,则效率很低。
选择重传通过重传那些它怀疑在接收方出错的分组且缓存因此失序的分组,从而避免了不必要的重传。
对于发送方:
- 从上层收到数据。和GBN一样,检查是否超出窗口大小。
- 超时。此时每个分组都有属于自己的逻辑定时器,因为超时发生后只能发送一个分组。可以使用单个硬件定时器模拟多个逻辑定时器的操作。
- 收到ACK。将被确认的分组标记为已接收。如果分组序号为send_base,则需要额外移动send_base指针,同时发送窗口中未发送的分组。
对于接收方:
- 序号在[rcv_base, rcv_base+N-1]的分组正确接收。只要收到分组完整且在窗口内产生一个选择ACK会送给发送方,如果序号等于基序号,则窗口右移。否则则缓存。如果右移之后的连续序号的分组都已经缓存则一并交付给上层。
- 序号在[rcv_base-N, rcv_base-1]的分组被正确收到,此时必须产生一个ACK,即使之前已经确认过了。
- 其他范围的序号直接忽略。
对于序号有限的情况下还会产生严重的同步问题:
如上两种不同的情况会产生相同的响应效果。
一般而言,窗口长度必须小于或等于序号空间大小的一半。否则,就会出现如上的同步问题。
3.4.5 总结
可靠数据传输的机制:
- 校验和
- 定时器
- 序号
- 确认
- 否定确认
- 窗口、流水线。
3.5 面向连接的运输:TCP
3.5.1 TCP连接
- TCP连接提供的是
全双工(full-duplex service)
的。即数据可在进程A流向进程B的同时,从进程B流向进程A。 - TCP连接是
点对点
的。两台主机是一对,3台主机则太多了。 三次握手(three-way handshake)
来建立连接,构建缓存等。- TCP从缓存中取出多少数据放入一个报文段受限于
最大报文段长度(Maximum Segment Size, MSS)
,MSS根据最大链路层帧长度(Maximum Transmission Unit, MTU)
来设置,以太网和PPP链路层协议都具有1500字节的MTU,而TCP/IP的首部长度为40字节,因此MSS为1460字节。
3.5.2 TCP报文段结构
- 源端口号(source port)和目的端口号(dest port)用于多路复用/分解。
- 校验和(Internet checksum)用于校验数据是否正确完整。
- 序号字段(Sequence number)和确认字段(Acknowledgment)用于可靠数据传输。
- 接收窗口字段(Receive window)用于拥塞控制。
- 4bit的首部长度(Header length)只是了以32bit字为单位的TCP首部长度,因为选项字段(Option)长度不定,所以TCP首部长度是可变的(Option为空时,首部为20字节)
- 可选与变长的选项字段(Options),用于接收方和发送方协商最大报文长度(MSS)时使用。
- 6bit的标志字段:
- ACK比特只是指明该分组为ACK分组,用于分组确认。
- RST、SYN、FIN比特
- CWR、ECE比特用于连接的建立与拆除
- PSH比特用于指示接收方是否应该立即将数据交给上层。
- URG比特用于指示报文段里存在着被发送端的上层实体置为“紧急”的数据,紧急数据的最后一个字节由16bit的紧急数据指针字段(Urgent data pointer)指出。当紧急数据存在并给出紧急数据尾指针时,TCP必须通知接收端的上层实体。
1.序号和确认号
序号是建立在传送的字节流上,而不是建立在传送的报文段的序列上。序号为该报文段首字节的字节流编号。
如图有500个报文段,每个报文段1000个字节,则第一个报文段的序号为0,第二个报文段的序号为1000。
确认号:主机A和主机B之间建立(全双工的)TCP连接,主机A填充进报文段的确认号是主机A期望从主机B收到的下一个字节的序号。假设主机A已经收到主机B的编号为0~535的所有字节,同时它打算发送一个报文段给B,则A就会把这个报文段的确认号字段设置为536。
因为TCP只确认该流中第一个丢失字节为止的字节,因此也被称为提供累积确认(cumulative acknowledgment)。
当TCP收到失序的报文段时,RFC没有规定怎么做,可以选择丢弃和缓存,由TCP的编程人员处理,通常实践中采用缓存方式。
Telnet:序号和确认号的一个学习案例
3.5.3 往返时间的估计与超时
估计往返时间:
TCP会维持一个SampleRTT均值(被称为EstimatedRTT)。一旦获得一个新SampleRTT时,TCP会根据下列公式更新EstimatedRTT:
E
s
t
i
m
a
t
e
d
R
T
T
=
(
1
−
α
)
⋅
E
s
t
i
m
a
t
e
d
R
T
T
+
α
⋅
S
a
m
p
l
e
R
T
T
EstimatedRTT=(1-\alpha)\cdot EstimatedRTT+\alpha \cdot SampleRTT
EstimatedRTT=(1−α)⋅EstimatedRTT+α⋅SampleRTT
在[RFC 6298]中给出的
α
\alpha
α推荐值为0.125。
统计学上,这种平均被称为指数加权平均(Exponential Weighted Moving Average, EWMA)。
同时定义了RTT偏差,用于估算SampleRTT一般偏离EstimatedRTT的程度:
D
e
v
R
T
T
=
(
1
−
β
)
⋅
D
e
v
R
T
T
+
β
⋅
∣
S
a
m
p
l
e
R
T
T
−
E
s
t
i
m
a
t
e
d
R
T
T
∣
DevRTT=(1-\beta)\cdot DevRTT+\beta \cdot |SampleRTT- EstimatedRTT|
DevRTT=(1−β)⋅DevRTT+β⋅∣SampleRTT−EstimatedRTT∣
β
\beta
β的推荐值为0.25
设置和管理重传超时间隔:
T
i
m
e
o
u
t
I
n
t
e
r
v
a
l
=
E
s
t
i
m
a
t
e
d
R
T
T
+
4
⋅
D
e
v
R
T
T
TimeoutInterval = EstimatedRTT+4\cdot DevRTT
TimeoutInterval=EstimatedRTT+4⋅DevRTT
推荐的初始TimeoutInterval值为1秒。同时出现超时后TimeoutInterval将加倍。然而只要收到报文段并更新EstimatedRTT,就是用上述公式再次计算TimeoutInterval。
3.5.4 可靠数据传输
TCP可靠数据传输的一个简化版本。
/*假设发送方不受TCP流量和拥塞控制的限制,来自上层数据的长度小于MSS,且数据传送只在一个方向进行*/
NextSeqnum = InitialSeqNumber;
SendBase = InitialSeqNumber;
loop (永远) {
switch (事件)
事件:从上面应用程序接收数据e
生成具有序号NextSeqNum的TCP报文段
if(定时器当前没有运行)
启动定时器
向IP传送报文段
NextSeqNum = NextSeqNum + lenght(data)
break;
事件:定时器超时
重传具有最小序号但仍未应答的报文段
启动定时器
break;
事件:收到ACK,具有ACK字段值y
if(y > SendBase)
SendBase = y
if(当前还有尚未确认的报文段)
启动定时器
break;
}
1.一些有趣的情况
情况一:
情况二:
只重传第一段,第二段不重传(似回退N步又不同)
情况三:
在超时间隔内收到ACK=120,即使ACK=100丢失,也不用重传第一个分组。
2.超时时间加倍
大多数TCP实现对超时时间进行修改。
每当超时时间发生,重传时定时器都会设置为原线值的两倍。即如果TimeoutInterval初始为0.75,超时后设置为1.5,如果还超时则设置为3。以指数数量级增加。
直到定时器的另外两个时间(即收到上层应用数据和收到ACK)中任意一个发生时,TimeoutInterval由最近的EstimatedRTT与DevRTT计算更新。
这样做的好处:定时器过期很可能是由于网络阻塞,此时如果超时时间仍用计算可得,会使拥塞更加严重。
3.快速重传
冗余ACK:当接收方收到的分组序号不是所期望的序号时,返回冗余ACK=期望序号。
如果一个报文段丢失,就很可能引起许多一个接一个的冗余ACK。如果TCP发送方接收到对相同数据的3个冗余ACK,它把这当作一种指示,说明跟在这个已被确认过3次的报文段之后的报文段已经丢失。一旦收到3个冗余ACK,TCP就执行快速重传。
事件:收到ACK,具有ACK字段值y
if(y > SendBase){
SendBase = y
if(当前还有尚未确认的报文段)
启动定时器
}else{
对y收到的冗余ACK数加1
if(对y收到的冗余ACK数==3)
重新发送具有序号y的报文段
}
4.是回退N步还是选择重传
TCP的确认是累积式的,且定时器只用1个,这么看来更像GBN(回退N步)的风格,但是对于失序的分组会缓存起来(像SR),而且TCP不会重传N个分组,而是至多重传1个分组。
TCP协议更像GBN和SR的混合体。
3.5.5 流量控制
- 流量控制:为防止接收方缓存溢出,遏制发送方TCP传输。
- 拥塞控制:为解决IP网络的拥塞,遏制发送方TCP传输。
TCP通过接收窗口(reveive window)来提供流量控制。该变量用于指示接收方还有多少可用的缓存空间。
假设主机A通过一条TCP连接向主机B发送一个大文件。主机B为其连接分配了一个接收缓存,并用RcvBuffer来表示其大小。主机B不时的从缓存中读取变量:
- LastByteRead:主机B上的应用进程中从缓存读出的数据流的最后一个字节的编码。
- LastByteRcvd:从网络中到达的并且已放入主机B接收缓存中的数据流的最后一个字节编号。
因此下式必须成立:
L a s t B y t e R c v d − L a s t B y t e R e a d ≤ R c v B u f f e r LastByteRcvd-LastByteRead\le RcvBuffer LastByteRcvd−LastByteRead≤RcvBuffer
接收窗口用rwnd表示,根据缓存可用空间数量来设置:
r w n d = R c v B u f f e r − ( L a s t B y t e R c v d − L a s t B y t e R e a d ) rwnd = RcvBuffer - (LastByteRcvd - LastByteRead) rwnd=RcvBuffer−(LastByteRcvd−LastByteRead)
连接是如何使用变量rwnd来提供流量控制服务呢?
主机B通过把当前的rwnd值放入它发给主机A的报文段接收窗口字段(receive window)中,一开始由 r w n d = R c v B u f f e r rwnd=RcvBuffer rwnd=RcvBuffer。
主机A轮流跟踪两个变量,LastByteSent和LastByteAcked,即最新发送的分组代表的字节数,以及已经确认了的分组的字节数,主机A要保证下面式子恒成立:
L a s t B y t e S e n t − L a s t B y t e A c k e d ≤ r w n d LastByteSent - LastByteAcked \le rwnd LastByteSent−LastByteAcked≤rwnd
有个小小问题:当rwnd为0时,再将rwnd=0通知给A之后,B没有任何数据发送给A,则此时A就不知道B的缓存有新的空间了,即A被阻塞了。为此TCP规范中要求:当主机B的接收窗口为0,主机A仍然继续发送只有一个字节数据的报文段,这些报文段将会被接收方确认。最终缓存被清空,并且确认报文中包含一个非0的rwnd值。
3.5.6 TCP连接管理
建立TCP连接(三次握手):
关闭一条TCP连接(四次挥手):
以下为一个客户TCP经历的典型TCP状态序列
以下为一个服务器TCP经历的典型TCP状态序列
3.6 拥塞控制原理
3.6.1 拥塞原因与代价
1.当两个发送方和一台具有无穷大缓存的路由器且两条连接共享同一个单跳路由时:
当发送方的向路由器提供流量的平均速率
λ
i
n
\lambda _{in}
λin从0逐渐增加到
R
/
2
R/2
R/2时,接收方的吞吐量
λ
o
u
t
\lambda _{out}
λout恒等于
λ
i
n
\lambda _{in}
λin,但是时延在
λ
i
n
\lambda _{in}
λin接近
R
/
2
R/2
R/2时,经历巨大排队时延(注意
λ
i
n
\lambda _{in}
λin为平均速率,具体原因见1.4.2),时延将趋于无穷。
2.当两个发送方和一台具有有限缓存的路由器时
此时路由器会产生丢包。
- 如果主机A能得知路由器缓存是否空闲从而在空闲时发送分组就有和无限缓存一样的效果 λ o u t \lambda _{out} λout恒等于 λ i n ′ \lambda _{in}' λin′。
- 如果发送方在确认了一个分组已经丢失才会重传,此时的 λ i n ′ \lambda _{in}' λin′有一部分包含了重传的数据。
- 如果发送方还会判断丢失失误,超时了但是没有丢失。此时
λ
i
n
′
\lambda _{in}'
λin′中还会有无意义重传的一部分。
3.4个发送方和具有有限缓存的多台路由器及多跳路径。
- 当 λ i n \lambda _{in} λin较小时, λ o u t \lambda _{out} λout会随之增加。
- 当
λ
i
n
\lambda _{in}
λin很大时,因为A-C流量与B-D流量竞争路由器R2,所以当B-D连接的供给载荷趋于无穷,R2的空闲缓存全部用于B-D连接,A-C连接在R2上的吞吐量为0。
3.6.2 拥塞控制方法
- 端到端拥塞控制。网络层没有为运输层拥塞控制提供显式支持。端系统通过TCP报文段的丢失或者往返时延的增加作为拥塞程度增加的指示。
- 网络辅助的拥塞控制。路由器向发送方提供关于网络中拥塞状态的显式反馈信息。可以通过直接反馈信息由路由器发送给发送方
拥塞分组(choke packet)
说明“我阻塞了”。也可以通过标记或更新流过的分组中某个字段来指示拥塞的产生,接收方收到一个标记分组后,向发送方通知网络拥塞指示(更通用的方式)。
3.7 TCP拥塞控制
3.7.1 经典的TCP拥塞控制
对比流量控制,拥塞控制还跟踪一个额外变量,即拥塞窗口(congestion window)
,表示为cwnd。我们有
L
a
s
t
B
y
t
e
S
e
n
t
−
L
a
s
t
B
y
t
e
A
c
k
e
d
≤
m
i
n
c
w
n
d
,
r
w
n
d
LastByteSent-LastByteAcked\le min{cwnd, rwnd}
LastByteSent−LastByteAcked≤mincwnd,rwnd
TCP发送方通过超时事件或者收到3个冗余ACK来判断出现丢包现象,进而判断出现路径上拥塞。
- 出现拥塞控制时(即出现丢包):发送方通过限制cwnd,进而限制了发送速率。
- 当没有出现拥塞控制(即没出现丢包):发送方增加窗口大小(cwnd),如果确认以较慢速率到达,则以较慢速率增加,如果确认以较快速率到达,则以较快速率增加cwnd。因此TCP也被称为
自计时(self-clocking)
的。
TCP的拥塞控制算法(TCP congestion control algorithm)
主要包括3个状态:
- 慢启动
- 拥塞避免
- 快速恢复
1.慢启动
当一条TCP连接开始时,cwnd的值通常初始设置为一个MSS的较小值,然后只要有传输的报文段被确认就将拥塞窗口增加一个MSS。一开始cwnd为1,如果没有丢包则变为2,然后如果还没有丢包则变为4(有两个报文段被确认,窗口增加2),然后8,16…以指数方式增长。
慢启动结束的三种情况:
- 如果存在一个由超时指示的丢包事件,TCP发送方将cwnd设置为1,并重新开始慢启动过程,并将第二个状态变量值ssthresh设置为cwnd/2(即检测到拥塞时,把ssthresh设置为拥塞窗口的一半)。
- 当检测到cwnd达到或超过ssthresh值时,继续将cwnd翻番可能有点鲁莽,因此此时结束慢启动,进而进入拥塞避免模式。
- 如果存在一个3个冗余ACK指示的丢包事件,此时TCP执行一种快速重传并进入快速恢复状态。
2.拥塞避免
进入拥塞避免状态时,cwnd的值大约为遇到拥塞时的一半。之后每个到达ACK,cwnd增加MSS/cwnd个字节,这样在收到所有cwnd个报文段后拥塞窗口增加了一个MSS。
拥塞避免结束的种情况:
- 当出现超时,TCP发送方将cwnd设置为1,并重新开始慢启动过程,并将第二个状态变量值ssthresh设置为cwnd/2
- 当出现3个冗余ACK时,ssthresh置为cwnd的一半,cwnd加3倍MSS。进入快速恢复状态。
3.快速恢复
- 收到冗余ACK,cnwd加MSS,继续处于快速恢复状态
- 收到新ACK,cwnd设置为ssthread,进入拥塞避免状态。
- 超时,将cwnd设置为1,并重新开始慢启动过程,并将第二个状态变量值ssthresh设置为cwnd/2。
4.Tachoe、Reno、CUBIC
早期的TCP版本TCP Tahoe,不论是超时还是收到3个冗余ACK,都按超时处理,没有快速恢复状态。而TCP的较新版本TCP Reno,则综合了快速恢复。
如图在回合1时进入慢启动状态,则回合8收到3个冗余ACK时的两个版本的拥塞窗口变化图如下:
因此Reno版本如果忽略开始时初始的慢启动阶段,假定丢包有3个冗余ACK而不是超时指示,TCP拥塞控制常被称为加性增、乘性减(Additive-Increase Multiplicative-Decrease, AIMD)拥塞控制方式,命名原因如下:
当发生丢包时的窗口长度为W,记一条连接的平均吞吐量为
一条连接的平均吞吐量
=
0.75
×
W
R
T
T
一条连接的平均吞吐量=\frac {0.75\times W} {RTT}
一条连接的平均吞吐量=RTT0.75×W
TCP CUBIC与TCP Reno有些许不同,仅仅改变了拥塞避免阶段,具体如下:
- 令 W m a x W_{max} Wmax为最后检测到丢包时TCP拥塞窗口的长度,令K为假定无丢包情况下当TCP CUBIC的窗口长度将再次到达 W m a x W_{max} Wmax时的未来时间点。
- CUBIC以当前事件t和K之间距离的立方为函数来增加拥塞窗口。
TCP CUBIC相较于Reno版本,获得了更高的发送速率。也是用于Linux操作系统的TCP默认版本。
3.7.2 网络辅助明确拥塞报告和基于时延的拥塞控制
1.明确拥塞报告
明确拥塞报告(Explicit Congestion Notification, ECN)
是一种网络辅助的拥塞控制形式,路由器使用一种ECN比特设置指示该路由器正在经历拥塞,该拥塞指示由被标记的IP数据报携带,送给目的主机,再由目的主机通知发送主机,发送主机收到后通过减半拥塞窗口来做出反应。
2.基于时延的拥塞控制
在TCP Vegas种,发送方对所有应答分组测量源到目的地路径的RTT,令
R
T
T
m
i
n
RTT_{min}
RTTmin是在发送方测量到的最小值,则在cwnd窗口大小下,如果没有拥塞,则吞吐量应该在
c
w
n
d
/
R
T
T
m
i
n
cwnd/RTT{min}
cwnd/RTTmin左右,如果检测吞吐量接近这个值则增加发送速率,如果比这个值大的多则减少发送速率。
3.7.3公平性
考虑两条TCP连接(AIMD)共享一段传输速率为R的链路的简单例子:
假定两条连接有相同的MSS和RTT的理想化情形中。
一开始位于点A,则二者吞吐量相加小于R,位于非阻塞状态,然后都线性增加,移动到B,同时检测到阻塞,然后减半到达C,然后随着不断迭代,会逐渐趋于Equal bandwidth share。达到公平(吞吐量近似相等)。
3.8 运输层功能的演化
除了TCP的经典版本TCP Tahoe和Reno外,一些新的版本CUBIC、DCTCP、CTCP、BBR等,统计结果显示CUBIC和CTCP已经比经典的TCP Reno更为广泛的部署在Web服务器上,BBR被部署在谷歌的内部B4网络以及谷歌面向公众的服务器上。
QUIC(应用层协议)
如果某些应用需要的运输服务需要比UDP更多,同时不需要TCP的所有特定所能,那么设计者能够在应用层构建自己的协议就是QUIC。当前超过7%的因特网流量来自QUIC。
QUIC是一个应用层协议,使用UDP作为底层运输层协议,并且专门为HTTP/2版本设计,将来综合进HTTP/3。QUIC的主要特征包括:
- 面向连接和安全:和TCP一样是面向连接的协议。QUIC将用于拆功能键连接状态的握手与用于界别和加密的握手;且所有QUIC分组都是加密的。
- 数据流:流是两个QUIC端点之间可靠、按序的双向数据交付的抽象。
- 可靠的eTCP友好的拥塞控制数传输