运输层详解
1.The model of Transport-layer services
运输层为运行在不同主机上的进程之间提供了通信,传输层协议是在终端中执行的,而网络层是主机之间的通信,传输层依赖于网络层服务,但又能增强网络层的服务
发送端:将应用程序消息分解为分段,传递到网络层 rcv 端:将段重新组合成消息,传递到应用层
TCP和UDP的分组统称为segment报文段,网络层的分组叫做datagram数据报
协议概述
TCP:
- 可靠、有序的交付
- 拥塞控制
- 流控制
- 连接设置
UDP
- 不可靠、无序递送
- “尽力而为”的 IP 的简洁扩展
- 不可进行延迟保证、带宽保证
2.multiplexing/demultiplexing (多路复用/分解)
一个进程有一个或多个socket套接字
multiplexing多路复用:从源主机的不同socket收集数据块,为每个数据块封装上首部header信息,生成报文段传输网络层
demultiplexing多路分解:收到网络层传来的报文段之后,根据首部解析并将当中的数据块交付给正确的socket
每个数据报都有源 IP 地址、目标 IP 地址,每个数据报携带 1 个传输层段,每个segment都有源端口号、目标端口号,主机使用IP 地址和端口号将分段定向到适当的套接字
2.1 UDP无连接的多路分解 Connectionless demultiplexing
由双元组标识的 UDP 套接字: (dest IP 地址,dest 端口号)
- 当主机接收到 UDP 段时:
- 检查网段中的目标端口号
- 将 UDP 段定向到具有该端口号的套接字
- 具有不同源 IP 地址和/或源端口号的 IP 数据报,这些数据报定向到同一套接字
2.2 TCP的多路分解 Connection-oriented demultiplexing
由 4 元组标识的 TCP 套接字: 源 IP 地址 源端口号 dest IP 地址 dest 端口号
recv 主机使用所有四个值将段定向到适当的套接字
- 服务器主机可能支持许多并发 TCP 套接字
- 每个套接字由其自己的 4 元组标识 Web 服务器,源ip或源端口号不同也将被定向到不同socket
- 对每个连接客户端具有不同的套接字
- 非持久性 HTTP 将为每个请求提供不同的套接字
3.UDP(User Datagram Protocol)用户数据报协议
为什么有 UDP?
- 无需连接建立(这可能会增加延迟),不用三次握手,没有建立连接的时延,是DNS建立在UDP之上的主要原因:
- 发送方、接收方无连接状态,不需要维护连接状态
- 分组首部开销小,TCP有20字节首部开销,UDP仅仅8字节
- 无拥塞控制:UDP 可以根据需要快速blast,一些应用需要最短时延,可以忍受一些数据丢失
实际应用:
- 通常用于流式传输多媒体应用程序: 容错性 速率敏感
- 其他 UDP 用途:域名解析(DNS) 断续器(SNMP)
- 通过 UDP 进行可靠传输:在应用层实现可靠性,如QUIC
UDP报文段结构:
header共4个字段,每个字段2字节,因此header总共8字节
length表示UDP报文段字节数,包含header的长度
checksum进行差错检验
UDP checksum
检验传输段是否产生错误,但不能保证没错
发送端: 将段内容视为 16 位整数序列
校验和:段内容的加法(1 的补码和)
发送方将checksum值放入 UDP的checksum字段
接收端:检查计算的校验和是否等于校验和字段
如果产生进位,把进位加到末位,然后取反得到Checksum的值
4.reliable data transfer(rdt) 可靠数据传输
4.1 rdt 1.0
假设底层信道完全可靠,没有位错误和数据丢失
直接发送方发数据给底层信道,接收方从底层信道读取
下面的图,横线上面表示引起变化的事件,横线下面代表事件变化后采取的动作(函数实现)
发送端等待上层的调用(应用层传数据过来)然后发送数据,接收端等待下层的调用(网络层传数据报过来)然后发送数据
4.2 rdt 2.0
假设:可能会发生bit错误、但起码不会丢包
解决:
- 错误检测:checksum以检测位错误
- 如何从错误中恢复:接收方反馈和重传机制 ARQ(Automatic Repeat reQuest)自动重传请求协议
- 确认 (ACK):接收方明确告诉发送方 pkt 接收正常
- 否定确认 (NAK):接收方明确告诉发送方 pkt 有错误 发件人在收到 NAK 时重传
rdt2.0 中的新机制(相比rdt1.0): 错误检测、接收器反馈:控制消息 (ACK,NAK)、重传
这是一种停等协议(stop-and-wait):
发送端等待上层的调用然后发送数据,然后等待ACK和NAK分组响应,收到响应后如果ACK继续等待上层调用,如果NAK则重传,继续等待ACK或NAK。
接收端等待下层的call,收到call后检验数据包,根据情况返回ACK或NAK分组
4.2.1 rdt 2.1
问题:ACK和NAK数据包损坏怎么办,不能简单重传
处理方式:让发送方给每个packet加序列号Seq,因此接收方只要检查序号就能判断是否为重传
发送方的状态图:反映了发送和接受的分组序号Seq,Seq只需要1bit
等待来自上层的Seq为0的分组
等到Seq为0的ACK和NAK
等待来自上层的Seq为1的分组
等到Seq为1的ACK和NAK
接收方状态图:根据发送的packet的Seq不同返回不同的ACK/NAK
4.2.2 rdt 2.2
没有NAK,只有ACK,因为发送方检查收到的ACK分组中的Seq就能替代NAK功能,接收到2个序号一样的ACK(冗余ACK),就知道接收方没有正确接受该序号之后的分组,然后重传
状态图:
4.3 rdt 3.0
假设:除了bit错误之外,底层信道还会丢包(数据包,ACK包)
这里让发送方来负责检测和恢复丢包
解决方案:超时(timeout)
- 发送方等待“合理”的 ACK 时间,如果此时未收到 ACK,则重新传输
- 如果 pkt(或 ACK)只是延迟(未丢失):
- 重新传输将是重复的,但是使用 seq 已经处理了这个问题
- 接收方必须指定正在 ACKed 的 pkt 的 seq
需要倒数计时器(countdown timer)
如下图,增加了timeout和用于计时的start_timer、stop_timer
超时重传机制如图所示
由于分组序号Seq在0和1之间交替,rdt 3.0有时候也称作比特交替协议(alternating-bit protocol)
性能问题:
假设信道发送速率1 Gbps,包含header和数据的分组长度1000字节(8000 bits),则发送一个分组进入该1 Gbps链路所需时间:
如上图,发送方忙于发送的时间很少,利用率0.00027,主要是由停等方式造成的
4.4 流水线(pipelining)
发送方不需要等待ACK响应,接下去发送分组
实现:
- 增加序列号Seq的范围
- 在发射方和接收方处有缓存
- 要解决损坏、丢失、超时问题,通过Go-Back-N(GBN回退N步), Selective Repeat(SR选择重传)
4.4.1 Go-Back-N(GBN回退N步)
下面为Seq字段的结构,绿色代表已确认的分组,黄色代表未确认但已发送,蓝色代表即将发送,白色部分不能被使用,N为窗口长度,若Seq字段长度为k bits,则序号空间可看作长度为
2
k
2^k
2k的环
发送方动作必须响应三种类型的事件:
- 上层调用。上层调用rdt-send(),检查发送窗口是否已满(是否已有N个已发送但是未确认的分组),未满即可调用,已满可用超时、缓存、同步机制等策略。
- 收到一个ACK。对序号为n的分组,进行累积ACK
- 超时。重传序号为n即以上的分组,即重传所有已发送但未确认的。
接收方动作: - ACK-only:始终为正确接收的 pkt 发送具有最大顺序 seq 的 ACK
- 可能会生成重复的 ACK
- 只需要记住预期的seqnum
- 对于无序的包:
- 丢弃(不进行缓存,因为会重传所以没必要)
- 依然发送之前最大顺序的ACK
具体表现:
4.4.2 选择重传SR(Selective repeat)
发送方:绿色已确认,蓝色即将发送,黄色发送了未确认
接收方:红色失序、但是已确认所以先缓存,等待其他顺序更小的;蓝色是已接受;灰色代表期待接收,但没收到
发送方动作:
- 从上层收到数据: 如果在窗口中检查到下一个可用的seq,则发送pkt
- timeout(n)超时:
- 重新发送 pkt n(第n个分组)
- 重新启动计时器
- 收到ACK:
- 若收到[sendbase,sendbase+N] 窗口中的 ACK(n),将 pkt n 标记为已接收
- 如果是最小的未确认pkt(分组序号等于send_base),则将窗口基序号向前移动到下一个未确认的ACK。
- 如果窗口移动了并且有序号落在窗口内未发送分组,则发送这些分组
接收方动作:
- pkt n 的seq in [rcvbase, rcvbase+N-1] :(在窗口当中)
- 发送ACK(n)
- 如果是无序的(不按照预期顺序):放入缓冲区
- 如果正常按顺序:交付(也交付缓冲的、有序的 pkts),提前窗口到下一个尚未收到的 pkt
- pkt n 的seq in [rcvbase-N,rcvbase-1] :(在最小seq之前)
- send ACK(n),即使以前已经确认过,否则发送方窗口滑动不了
- 其他情况:忽视分组
具体表现:
图中pkt2丢包,接收端窗口最小序号rcvbase停在2,发送端由于没收到ACK2,所以sendbase停在2。
发出pkt5后,发送端窗口已满,超时,重传pkt2,接收端收到pkt2后返回ACK2马上移动窗口,发送端收到ACK2也移动窗口
如果序号空间太小,可能无法判断是新发送的数据还是之前的重传,因此窗口长度必须不大于序号空间大小的一半
5.TCP: Transmission Control Protocol 传输控制协议
5.1 特点
- 点对点: 一个发送方,一个接收方(所以不能“广播”)
- 可靠、按顺序排列的字节流: 没有“信息边界”
- 流水线: TCP 拥塞和流量控制,设置窗口大小
- 具有发送和接收缓冲区
- 全双工数据(full-duplex data):
- 同一连接中的双向数据流 (bi-directional data flow)
- MSS(Maximum Segment Size):最大报文段长度(应用层数据),通常根据MTU(Maximum Transmission Unit)来设置,由于MSS单指应用层数据段最大长度,因此有 M S S = M T U − T C P / U D P h e a d e r − I P h e a d e r MSS = MTU - TCP/UDP header - IP header MSS=MTU−TCP/UDPheader−IPheader,例如以太网和PPP链路层协议MTU=1500,TCP报文的MSS=1460,UDP的MSS=1472
- 面向连接(connection-oriented): 握手(交换控制消息),以初始化发送方,接收方在数据交换前的状态
- 流量控制: 发送方不会发过多数据淹没接收方
5.2 TCP报文段结构
- Seq. #'s: 段数据中第一个字节的字节流“编号”(因为不是按segment计数,是按byte计数)
- ACKs:
- ACK是主机正在等待的数据的下一字节的Seq
- 累积确认,累积确认到流中至第一个丢失为止的字节
- Q:接收方如何处理无序段 A:TCP规范没有说,取决于实现者
5.3 RTT(Round Trip Time)往返时间的估计与超时
- 估计RTT的计算,典型的α值为0.125
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)*EstimatedRTT + \alpha*SampleRTT EstimatedRTT=(1−α)∗EstimatedRTT+α∗SampleRTT - 样本RTT与估计RTT的偏差程度计算,典型的β值为0.25
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)*DevRTT +\beta*|SampleRTT-EstimatedRTT| DevRTT=(1−β)∗DevRTT+β∗∣SampleRTT−EstimatedRTT∣ - 则可算出超时间隔
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*DevRTT TimeoutInterval=EstimatedRTT+4∗DevRTT
5.4 TCP reliable data transfer(可靠数据传输)
可靠数据传输确保一个进程从接收缓存中读取的数据流是无损坏、无间隙、非冗余和按顺序的数据流,即收到的字节流与发出时相同
TCP 在 IP 不可靠的服务之上创建 rdt(reliable data transfer)服务
- 流水线
- 累积ACK
- 缓存无序的segment
- TCP 使用单一的重传计时器(计时器管理开销大)
重传由以下因素触发:
- 超时事件
- 重复ACK
TCP发送端3个与发送或重传有关的事件:
- 收到来自应用层的数据:
- 创建区段seq #
- seq # 是段中第一个数据字节的字节流数
- 启动计时器(如果尚未运行)(将计时器视为最旧的未确认段)
- 定时器过期间隔:超时间隔
- 超时:
- 重传导致超时的段 (只重传第一个,如果后面的也超时就后面再说,因为只有一个计时器)
- 重启计时器
- Ack rcvd: 如果确认以前未确认的段
- 更新已知已确认的内容
- 启动计时器(如果有未完成的段)
三种比较特殊的情况:
- 因为ACK丢失,时间间隔内发送端没有收到ACK,重传
- 因为ACK在超时间隔过后发送端才收到,而引起超时的段的重传已发送,收到的ACK是后面累积过后的ACK
- 第一个ACK丢失,但是第二个发送端在超时间隔内收到了,由于ACK比预想中的大,Host A知道先前的包Host B也收到了,因此没有重传
TCP接收端产生ACK的事件和过程:
事件 | TCP接收方动作 |
---|---|
所期望的seq到达,并且期望seq之前的数据都已经确认(发了ACK) | delayed ACK。等另一个按序报文段500ms,如果没到就发一个ACK |
所期望的seq到达,但前面还有一个按序报文段等着发ACK | 立即发送单个累计后的ACK,发送端收到后同时确认两个报文段 |
比期望seq大的到达 | 发一个冗余(duplicate)ACK,指出下一个期待的byte的seq |
能部分或者完全填充之前的seq数据间隔报文段到达 | 如果是未确认的最小seq,马上发ACK |
5.5 Fast Retransmit 快速重传
快速重传:在该报文段计时器过期之前重新发送报文段,而不等到超时
如果发送方收到相同数据的 3 个 ACK,则确定这个报文段后面发送的那一个报文段没收到,进行快速重传那个报文段。
选择确认(Selective Acknowledgement)类似于选择重传
5.6 Flow Control 流量控制
背景:TCP 连接的接收端具有接收缓冲区,但可能因为应用层可能从下层读取信息很慢而溢出(发送端不需要担心类似问题),所以有了流量控制(因为UDP没有,所以溢出就丢失了)
流量控制是一个速度匹配服务:将发送速率与接收应用的消耗速率匹配
动态设置接收窗口空间:
LastByteRcvd表示网络中到达并且放入主机B缓存的数据流最后一个字节编号
LastByteRead是主机B应用层从缓存读出的最后一个字节的编号
式子表示:
窗
口
空
间
=
缓
存
可
用
空
间
−
已
到
达
数
据
空
间
+
已
读
取
数
据
空
间
窗口空间=缓存可用空间-已到达数据空间+已读取数据空间
窗口空间=缓存可用空间−已到达数据空间+已读取数据空间
R
c
v
W
i
n
d
o
w
=
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
]
RcvWindow = RcvBuffer-[LastByteRcvd - LastByteRead]
RcvWindow=RcvBuffer−[LastByteRcvd−LastByteRead]
后两个都是变量,窗口空间跟着这两个变量的变化而变化
5.7 TCP Connection Management 连接管理
5.7.1 TCP三次握手(建立连接)
- 步骤 1:客户端主机将 TCP SYN报文段(SYN置为1就叫做SYN报文段)发送到服务器
- 客户端随机指定初始seq#放到SYN报文段的seq里面
- 不含应用层数据
- 步骤 2:服务器主机接收SYN,使用SYNACK报文段回复
- server分配缓冲区
- server随机指定初始seq#放到SYNACK报文段的seq里面
- SYNACK中的ACK = client_isn(客户端指定的初始Seq) + 1
- 步骤 3:客户端接收SYNACK,使用ACK报文段回复,此时SYN置0,其中可能包含应用层数据
扩展:SYN flood attack,使用cookie机制防御(可以深入了解)
5.7.2 关闭连接
- 步骤 1:客户端系统将 TCP FIN 控制报文段(即FIN位被置1的报文段)发送到服务器
- 步骤 2:服务器接收 FIN,使用 ACK 进行回复。关闭连接,发送 FIN报文段
- 步骤 3:客户端收到FIN报文段,使用ACK回复。
- 步骤 4:服务器,接收ACK,连接关闭。
5.7.3 客户端状态转移图
5.7.4 服务端状态转移图
6.Congestion Control 拥塞控制
非正式的定义:“太多的来源发送太多的数据,太快,网络无法处理” ,不同于流量控制
事件:
- 丢失数据包(路由器的缓冲区溢出)
- 长时间延迟(在路由器缓冲区中排队)
6.1 拥塞的原因与代价分析
第1种情况(scenario 1):两个发送方、一个路由器、路由器缓存无限,无重传
λ
i
\lambda_i
λi代表主机A向路由器发出数据的速率
λ
i
\lambda_i
λi字节/秒
假设B与A相同的方式运行
C、R代表共享链路的容量
第2种情况(scenario 2):有丢包重传,路由器缓存有限
λ
i
′
\lambda'_i
λi′代表主机A向路由器发出数据的速率加上重传数据的速率
- a图表示不丢包的情况
- b图仅在丢失时重传(R/3初始数据,R/6重传数据是所有包都丢失的极端情况)
- c图提前发生超时,产生延迟(未丢失)数据包的不必要重传,所有包都提前发生超时则每个分组都通过链路两次
第3种情况(scenario 3):4个发送端、多跳路径、有超时重传
另外一种开销:第一跳路由器转发的分组在第二跳被丢弃
6.2 拥塞控制方法
端到端(end-end)拥塞控制:
- 运输层没有来自网络层的明确反馈
- 从端系统观测到的损失、延迟推断出拥塞
- TCP采取这种方法,因为IP层不会向端提供有关网络拥塞的反馈
网络辅助拥塞控制:
- 路由器向端系统提供反馈
- 单个bit(SNA、DECbit、TCP/IP的ECN(explicit congestion notification)、ATM)来指示拥塞
扩展样例:ATM ABR congestion control
6.3 TCP拥塞控制
发送方限制发送速率的手段:
TCP跟踪一个拥塞窗口(congestion window),该变量表示为cwnd,对TCP发送方发送的速率进行限制
并且发送方中未确认(没收到ACK)的数据量不会超过cwnd和rwnd(接收窗口)最小值
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 <= min\{cwnd,rwnd\}
LastByteSent−LastByteAcked<=min{cwnd,rwnd}
先假设rwnd无限大,则
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
<
=
c
w
n
d
LastByteSent-LastByteAcked <= cwnd
LastByteSent−LastByteAcked<=cwnd
且发送速率
r
a
t
e
=
c
w
n
d
/
R
T
T
rate = cwnd / RTT
rate=cwnd/RTT
发送方如何感知拥塞:
- 丢包事件 = 超时或3 个冗余ACKs(即一共收到4个相同ACK)
- TCP 发送方在丢失事件后降低速率 (本质为减少CongWin)
自计时(self-clocking):
- 发送方收到以前未确认报文段的ACK,判断为恢复正常并增大发送速率
- 带宽探测:根据收到ACK的速率增长窗口长度,收到ACK快则增长快
6.3.1 慢启动(Slow Start)
当连接开始时,以指数级速度(乘2)快速增加速率,直到第一次丢包事件
实现:
- 开始时拥塞窗口为1MSS(Maximum Segment Size,最大报文长度)
- 收到每个ACK就增加1MSS的CongWin(拥塞窗口),实际上就是每一次RTT后cwnd都翻倍
c
w
n
d
∗
=
2
cwnd*=2
cwnd∗=2
慢启动的结束:
- 慢启动期间如果出现丢包,把ssthresh(慢启动阈值)设置为cwnd/2,拥塞窗口重新设置为1MSS,重新进行慢启动
- cwnd到达或超过ssthresh时,结束慢启动(不再将cwnd乘2)进入拥塞避免模式
- 检测到3个冗余ACK,执行快速重传,cwnd值减半即cwnd/=2,ssthresh设置为cwnd/2,进入快速恢复状态
6.3.2 拥塞避免(TCP Congestion Avoidance)
在拥塞避免模式下,每一次RTT拥塞窗口的值增加1MSS即 c w n d + = 1 M S S cwnd+=1MSS cwnd+=1MSS
拥塞避免的结束:
- 超时,把ssthresh(慢启动阈值)设置为cwnd/2,拥塞窗口重新设置为1MSS,进入慢启动
- 检测到3个冗余ACK,执行快速重传,cwnd值减半即cwnd/=2,ssthresh设置为cwnd/2,进入快速恢复状态
6.3.3 快速恢复(Refinement)
cwnd值减半(但不会小于1MSS)后,每收到一个冗余ACK,cwnd加1MSS即 c w n d + = 1 M S S cwnd+=1MSS cwnd+=1MSS
即使收到3个冗余ACK,cwnd仍旧正常加3MSS
快速恢复的结束:
- 超时,把ssthresh(慢启动阈值)设置为cwnd/2,拥塞窗口重新设置为1MSS,进入慢启动
- 收到丢失报文段的正常ACK,即进入拥塞避免
6.3.4 TCP拥塞控制回顾
方法:提高传输速率(窗口大小),探测可用带宽,直到发生损失
- 加性增(Additive increase):每RTT增加1 MSS,直到检测到损失
- 乘性减(Multiplicative decrease):亏损后将CongWin减到一半
总结:
- 当CongWin低于阈值时,发送方处于慢启动阶段,窗口呈指数级增长。
- 当CongWin高于阈值时,发送方处于拥塞避免阶段,窗口线性增长。
- 当收到3个冗余 ACK 时,“阈值”设置为 CongWin/2,将“CongWin”设置为“阈值”。
- 发生超时时,阈值设置为 CongWin/2,CongWin 设置为 1 MSS。
6.3.5 TCP吞吐量(throughput)
作为窗口大小和RTT的函数,整个TCP的平均值是多少?假设忽略慢启动(因为只有刚开始建立连接为慢启动)
设 W 为发生损失时的窗口大小,则吞吐量为
W
∗
M
S
S
/
R
T
T
W*MSS/RTT
W∗MSS/RTT
在丢失后,窗口下降到 W/2,吞吐量下降到$ W*MSS/2RTT$
由于拥塞控制阶段线性增加,可以使用平均,整个过程中的平均吞吐量:
0.75
W
∗
M
S
S
/
R
T
T
0.75 W*MSS/RTT
0.75W∗MSS/RTT
6.3.6 公平性
公平性目标:如果 K个 TCP 会话共享相同的带宽 R 瓶颈链路,则每个会话的平均速率应为 R/K
两个相互竞争的sessions: 线性增加使斜率趋近为 1,乘法减少只是按比例降低吞吐量
所以在过程中,越来越趋近于等分带宽
公平性和 UDP:
- 多媒体应用程序通常不使用 TCP 因为不希望速率受到拥塞控制的限制
- 所以改用 UDP: 以恒定速率传输音频/视频,可以容忍数据包丢失
公平性和并行 TCP 连接:
没有什么可以阻止应用程序在2个主机之间打开并行连接。比如Web 浏览器
示例:
假设开始时速率R的链接支持着9个连接;
新应用程序要求1个TCP,获得速率R / 10
新应用程序要求11个TCP,得到R / 2!
7.扩展协议
QUIC、TCP ECN、TFRC(TCP友好控制)、DCCP、SCTP、TFRC