第三章 运输层
3.1 概述和运输层服务
3.1.1 运输层和网络层的关系
- 网络层提供了 主机 之间的逻辑通信。而运输层为运行在 不同主机上的进程 提供逻辑通信。
- 运输层协议只工作在端系统上。
- 运输协议能提供的服务受制于底层网络协议的服务模型。
- 网络层协议,即 IP 协议,其服务模型是 尽力而为交付服务 ,但不做任何的保证。是 不可靠服务 。
3.1.2 因特网运输层概述
- UDP 提供了一种不可靠、无连接的服务。
- TCP 提供了一种可靠的、面向连接的服务。
- 进程到进程的数据交付和差错检测是两种最低限度的运输层服务,也是 UDP 能提供的仅有的两种服务。
3.2 多路复用与多路分解
- 多路分解: 将运输层报文段中的数据交付到正确的套接字。
- 多路复用: 在源主机从不同的套接字中收集数据块,并为每个数据块封装上首部信息从而生成报文段,然后将报文传递到网络层。
- 运输层多路复用要求:
- 套接字有唯一标识符。
- 每个报文段有特殊字符(端口号)来指示该报文段所要交付到的套接字。
- 端口号:一个 16 比特的数,在 0 ~ 65535 之间。其中 0 ~ 1023 是周知端口号,是受限制的。
3.2.1 无连接的多路复用与多路分解
- 用
clientSocket = socket(socket.AF_INET, socket.SOCK_DGRAM)
创建一个 UDP 套接字时,系统会自动为该套接字分配一个未被其他 UDP 套接字使用的端口号。
也可以用clientSocket.bind(('',23333))
显式的分配一个端口号。 - 通常,应用程序的客户端让运输层自动地分配端口号,而服务器则分配一个特定的端口号。
- 一个 UDP 套接字是由一个包含 目的 IP 地址 和 目的端口号 的 二元组 标识的。
3.2.2 面向连接的多路复用与多路分解
- TCP 套接字是由一个 四元组(源 IP 地址,源端口号,目的 IP 地址,目的端口号)来标识的。
3.3 无连接运输:UDP
- 选择 UDP 的原因:
- 关于何时、发送什么数据的应用层控制更为精细。
- 无需连接建立。
- 无连接状态。
- 分组首部开销小。
- 用 UDP 建立可靠数据传输机制可以在应用层中实现。
3.3.1 UDP 报文结构
- 首部只有 4 个字段,每个字段 2 字节。其中,length 指示了 UDP 报文段中的 首部加数据的字节数。
3.3.2 UDP 检验和(上图中的 checksum)
-
检验和 是 UDP 的差错检测机制。其实现方法是:
- 将 UDP 报文按 16 比特进行分组。
- 将两个 16 比特的数字相加得到一个 32 位的数。
- 将 2 中得到的 32 比特数的 高 16 位和低 16 相加。
- 重复第 2 步直到得到的和的高 16 位为 0。
- 将得到的 16 比特的数按位取反即为 检验和。
- 注:计算之前的检验和为 0
- 如果传输没有出错,则在接收方处计算的的检验和将是
11111111111111
。若不是,则数据有错。
3.4 可靠数据传输原理
3.4.1 构造可靠数据传输协议
在这节将一步步研究一系列协议,它们一个比一个复杂,最后得到一个无错、完全可靠的数据传输协议。
1. 经完全可靠信道的可靠传输协议: rdt 1.0
- 这是最简单的情况,假设底层信道是完全可靠的。
-
有限状态机(FSM):
- 箭头指示了协议从一个状态变迁到另一个状态。
- 引起变迁的事件显示在横线的上方。
- 事件发生时所采取的动作显示在横线的下方。
- ^ 表示什么都不做。
下图是发送方和接收方的 有限状态机:
- 发送方:
-
rdt_send(data)
: 接收较高层的数据。 -
make_pkt(data)
: 将数据打包成分组。 -
udt_send(packet)
: 将分组发送到信道中。
-
- 接收方:
-
rdt_rcv(data)
: 从底层信道接收一个分组。 -
extract(packet,data)
: 从分组中取出数据。 -
deliver_data(data)
: 将数据传送给较高层。
-
2. 经具有比特差错信道的可靠数据传输:rdt 2.0
- 假设底层信道传输的数据可能会有比特差错。但是仍然不会丢包。
- 在接收方的协议中加入:
- 差错检测
-
接收方反馈
- 肯定确认(ACK)
- 否定确认(NAK)
-
重传
接收到有差错的分组时,发送方重传该分组。
- 下图是 rdt 2.0 的 FSM:
- 当发送方处于等待 ACK 或 NAK 的状态时,它不能从上层获得数据。这被称为 停等协议。
2.1 rdt 2.1
- rdt 2.0 中有一个致命的缺陷,就是没有考虑到 ACK 和 NAK 分组受损的可能性。
- 解决这个新问题的一个简单的方法就是在数据分组中添加一个字段,让发送方对其数据分组编号,即将发送数据分组的 序号 放在该字段。
- 于是,接收方只需要检查序号即可确定收到的分组是否一次重传。
- 对于停等协议这种简单的情况,1 比特的序号就足够了。
- 下图是 rdt 2.1 的 FSM:
2.2 rdt 2.2
- 当收到受损的分组时,rdt 2.0 的接收方会发送一个否定确认 NAK。
- 如果不发送NAK,而是对上次正确接收的分组发送一个 ACK,则也能实现和 NAK 一样的效果。
- 下图是 rdt 2.2 的 FSM:
3. 经具有比特差错的丢包信道的可靠数据传输:rdt 3.0
- 现在假定除了比特受损外,底层信道还会丢包。
- 在 rdt 3.0 中,丢包的问题让发送方解决。不管是发送的分组丢失,还是接收方返回的确认分组丢失,只要在经过一定的时延后,让发送方重发该分组即可。由此产生的 冗余数据分组 则由接收方通过序号处理。
- 下图是 rdt 3.0 发送方的 FSM:
- 因为分组序号在 0 和 1 之间交替,因此 rdt 3.0 有时被称为 比特交替协议。
3.4.2 流水线可靠数据传输
- rdt 3.0 是一个功能正确的协议,但是由于它是一个停等协议,大部分的时间都浪费在等待确认上面,所以性能不好。
- 解决这种特殊性能问题的一个简单的方法是:不使用停等方式运行,允许发送方发送多个分组而无需等待确认。这种技术被称为 流水线。
- 要使用流水线技术,则须:
- 增加序号范围。因为要传送多个分组,而每个传输中的分组必须有一个单独的序号。
- 协议的发送方和接收方两端必须能缓存多个分组。发送方至少得能缓存那些已发送但未确认的分组,而接收方或许也需要缓存那些已经正确接收的分组。
- 所需序号的范围和对缓冲的要求取决于数据传输协议如何处理丢失、损坏及延时过大的分组。
- 流水线的差错恢复有两种基本方法:
- 回退 N 步
- 选择重传
3.4.3 回退 N 步(GBN)
- 在 回退 N 步协议 中,允许发送方发送多个分组而不需等待确认,但它也受限于在流水线中未确认的分组数不能超过某个最大允许数 N。
- 基序号(base)为最早的未确认分组的序号。
- 下一个序号(nextseqnum)为下一个待发分组的的序号。
- N 常被称为 窗口长度,GBN 协议也常被称为 滑动窗口协议。
- 在实践中,一个分组的序号承载在分组首部的一个固定长度的的字段中。
- 如果该字段的比特数是 k ,则该序号范围是 [0,2k−1],是一个长度为 2k 的环。
- 下图是 GBN 发送方的扩展 FSM 描述:
- GBN 的发送方必须响应三种类型的事件:
- 上层的调用:若窗口未满,则产生一个分组将其发送。
- 收到一个 ACK:对序号为 n 的分组的确认采取 累积确认,表明接收方已正确接收到包括 n 的序号在内的 n 的以前的所有分组。
- 超时事件:只使用一个定时器,即最早的已发送但未被确认的分组所使用的定时器。如果出现超时,则发送方重传所有已发送但还未被确认的分组。如果收到一个 ACK,但仍有已发送但未被确认的分组,则重启定时器。如果没有已发送但未确认的分组,该定时器被终止。
- GBN 的发送方必须响应三种类型的事件:
- 下图是GBN 接收方的扩展 FSM 描述:
- 如果一个序号为 n 的分组被正确收到,并且按序(上次交付给上层的数据的序号是 n-1),则接收方为分组 n 发送一个 ACK,并将该分组中的数据交付到上层。在其他所有情况下,接收方丢弃该分组。并为最近按序接收的分组重新发送 ACK。
3.4.4 选择重传(SR)
- GBN 也存在一些性能问题,单个分组的错误会引起大量分组的重传。
- 选择重传 通过让发送方仅重传那些它怀疑在接收方出错的分组而避免了不必要的重传。
- 下图是选择重传发送方和接收方的序号空间:
- SR 发送方的事件和动作:
- 从上层接收数据: 检查下一个可用于该分组的序号,若在发送方的窗口内,则将数据打包发送。
- 超时: 定时器再次用来防止丢失分组。但是现在每个分组必须得有单独的定时器。
- 收到 ACK:倘若该分组序号在窗口内,则 SR 发送方将那个被确认的分组标记为已接收。如果该分组的序号等于send_base,则窗口基序号向前移动到具有最小序号的未确认分组处。如果窗口移动了并且该序号落在窗口内的未发送分组,则发送这些分组。
- SR 接收方将确认一个正确接收的分组而不管其是否按序。
-
SR 接收方的事件于动作:
- 序号在 [rcv_base, rcv_base + N -1] 内的分组被正确接收:在此情况下,收到的分组落在接收方的窗口内,一个选择 ACK 被回送给发送方。如果该分组以前没收到过,则缓存该分组。如果该分组的序号等于接收窗口的基序号,则该分组及以前缓存的序号连续的分组交付给上层。
- 序号在 [rcv_base - N, rcv_base - 1] 内的分组被正确接收: 产生一个 ACK,即使该分组是接收方以前已确认过的分组。
- 其他情况:忽略该分组。
-
窗口长度必须小于或等于序号空间大小的一半。
TCP可靠数据传输
TCP可靠数据传输概述
TCP在IP层提供的不可靠服务的基础上实现可靠的数据传输服务
流水线机制
累积确定
触发重传的事件
- 超时
- 收到重复ACK
RTT和超时
如何设置定时器的超时时间,希望设置成大于RTT,但是RTT是动态的
如果设置的过短,会引起不必要的重传
如果设置的过长,则会对段丢失的反应慢
如何估计RTT?
SampleRTT:测量从段发出去到收到ACK的时间忽略重传
SampleRTT的变化:测量多个SampleRTT,求平均值,形成RTT的估计值EstimatedRTT
EstimatedRTT = (1-a) * EstimatedRTT + a * SampleRtt
加权指数平均,典型值: 0.125
定时器超时时间的设置:
- EstimatedRTT + “安全边界”
- EstimatedRTT变化大 -> 较大的边界
测量RTT的变化值: SampleRTT与EstimatedRTT的差值
DevRTT = (1 - B) * DevRTT + B * |SampleRTT - EstimatedRTT|
B典型值 : 0.25
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
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
快速重传机制
TCP 的实现中,如果发生超时,超时时间间隔将重新设置,即将超时时间间隔加倍,导致其很大
-
重发丢失的分组之前要等待很长时间
通过重复ACK检测分组丢失 -
Sender会背靠背地发送多个分组
- 如果某个分组丢失,可能会引发多个重复的ACK
如果sender收到对同一数据的3个ACK,则假定该数据之后的段已经丢失
- 快速重传:在定时器超时之前即进行重传
快速重传算法
event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase = y
if (there are currently not-yet-acknowledged segments)
start timer
}
else {
increment count of dup ACKs received for y
if (count of dup ACKs received for y = 3) {
resend segment with sequence number y
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
TCP连接管理
TCP sender和receiver在传输数据前需要建立连接
初始化TCP变量
- Seq.#
- Buffer和流量控制信息
Client: 连接发起者
Server: 等待客户端请求 (具体可参见上面的java代码)
握手步骤:
1. Client发送syn 报文给Server(syn标识位置1)
- 给出初始seq
-
没有数据
- Server持有syn报文回送syn_ack报文(syn标识位置1)
-
Server分配缓冲区(syn泛洪攻击)
-
确定Server初始的seq
- 客户端收到syn_ack报文,返回ack报文.这次的ack可以带有初始数据(syn标识位置0)
四次挥手步骤
1. Client向Server发送FIN控制Segment
2. Server收到FIN,回复ACK,关闭连接,发送FIN
3. Client收到FIN,回复ACK
* 等待一段时间(怕Client发出去的ACK丢失)
4. Server收到ACK,连接关闭
步骤图:
SYN泛洪攻击
原理
攻击者发送大量的syn分组但是不对该分组进行响应,让Server端开启大量buffer浪费服务器端空间
防范
- 减少syn等待时间(不建议)
- syn缓存.
- 接收syn之后用hash表维护半开连接,等待下一次数据到来在开启真正的TCP缓存块.使用来源的一些信息(端口号)做hash,防止hashtable溢出
- 增大总连接缓冲区的大小
- 防火墙
TCP流量控制
接收方会为TCP连接分配buffer,如果发送方发的太快会导致接收方的buffer溢出,因此需要引入速度匹配机制
接收方会在Segment的头部字段将RcvWindow(接收窗口)的值告诉发送方,发送方限制自己已经发送但是还未收到ACK的数据不超过接收方的空闲RcvWindow尺寸
如果RcvWindow为0,会发生死锁.
所以其实发送方还是可以发送一个很小的段,以便捎回来一个新的RcvWindow.
拥塞控制原理
拥塞
非正式定义:”太多主机发送了太多数据或者发送速度太快,以至于网络无法处理”
表现为:
1. 分组丢失(路由器缓存溢出)
2. 分组延迟过大
拥塞的三个代价:
1. 拥塞时,分组的延时达到最大
2. 拥塞时,分组会导致路由器缓冲区溢出,导致丢失分组
3. 拥塞时,多跳网络中前几跳的努力会因为某一跳路由器的丢失分组而白费
当拥塞到一定程度时,整个网络将无法传输数据
端到端的拥塞控制方法:
1. 网络层不提供显式的支持
2. 端系统观察丢包和延迟等网络行为判断是否发生了拥塞
3. TCP采取这种方法
网络辅助的拥塞控制:
1. 路由器向发送方显式的反馈网络的拥塞信息
2. 简单的拥塞指示(1bit):SNA,DECbit,TCP/IP ECN,ATM
3. 指示发送方应该采取何种速率(定量)
ATM ABR网络中的拥塞控制
ARB:avaliable bit rate
* “弹性服务”
* 如果发送方路径”underloaded”就使用可用带宽
* 如果发送方路径拥塞就将发送速率降低到最低保障速率
RM(resource management)cells:专门用来指示网络拥塞状况的,穿插在正常的cell中发送
- 发送方发送
-
交换机设置RM cell位(网络辅助)
- NI bit: rate不许增长
- CI bit : 拥塞指示
- RM cell由接收方返回给发送方
总结起来就是发送方发出去,交换机和接收方置位,然后接收方统一返回给发送方
RMcell中还有显式的ER字段:两个字节(定量的指示)
- 拥塞的交换机可以将ER置为更低的值
- 发送方获知路径所能支持的最小速率
普通的RMcell中也有一个EFCI位指示拥塞,由交换机控制
如果RM cell前面的data cell的EFCI位被设为1,那么接收方在返回的RM cell中置CI位
TCP拥塞控制
TCP使用端到端的拥塞控制而不是使用网络辅助的拥塞控制,因为IP层不会向端系统提供显式的反馈.
设cwnd为拥塞窗口的大小,并假定发送方能通过cwnd调节发送速率
MSS为最大报文段大小
如果ack以非常慢的速度到达,则拥塞窗口将以非常慢的速度增加.另一个方面,如果确认以高速率到达,则该拥塞窗口将会更为迅速的增大.因为TCP使用确认来触发增大他的拥塞窗口的长度.
TCP的拥塞控制算法包含3个部分
加性增,乘性减:AIMD
- 慢启动
- 拥塞避免
- 快速恢复
慢启动
当一个TCP连接开始时,cwnd初始值为一个MSS的最小值,这时可用带宽远大于发送速率
所以,在慢启动阶段,每收到一个ACK,则拥塞窗口增加一个MSS.这样下去,每过一个RTT,拥塞窗口翻倍
什么时候结束这种指数增长呢?
* 如果检测到丢包事件,TCP将cwnd设置为1并重新开始慢启动,并且将一个变量threshold置为当前拥塞窗口的一半
* 如果当cwnd的值达到threshold时,将进入拥塞避免模式(不再翻翻,线性增加)
* 如果检测到三个ACK(并没有丢包那么严重,还能传一点东西),执行快重传并进入快恢复状态
拥塞避免
拥塞避免同样会增加cwnd,但是和慢启动不同,拥塞避免会每个RTT增加一个MSS(线性的)
如何结束拥塞避免呢?
- 超时时,和慢启动一样将cwnd设置为1并重新开始慢启动,并且将一个变量threshold置为当前拥塞窗口的一半
- 三个ACK时,将cwnd减半(为使测量结果更好,将threshold增加3个MSS).并且将threshold设为cwnd的一半.
快速恢复
对于引起进入快速恢复的每个冗余ACK,cwnd增加一个MSS.当最后一个ACK到达时,进入拥塞避免.如果出现超时事件,快速恢复在执行如同在慢启动和拥塞避免中相同的动作后,迁移到慢启动状态:当丢包事件发生后,cwnd被设置为一个MSS,并且threshold的值被设置为cwnd的一半.
拥塞控制总结
由于TCP Tahoe版本已经不再使用,这里只讨论Reno版本中的拥塞控制
- 初始状态为慢启动状态,threshold选定一个初始值(具体怎么选的依赖具体实现)
- 当cwnd小于threshold时,发送方慢启动状态,窗口指数增长
- 当cwnd大于threshold时,发送方拥塞避免状态,窗口线性增长
- 当发生3个ACK事件时先threshold = cwnd/2,然后cwnd = threshold,重新进入拥塞避免状态(还会加上3个ACK的)
- 当发生超时事件时,threshold = cwnd/2,cwnd = 1 ,然后进入慢启动状态
微信公众号【黄小斜】大厂程序员,互联网行业新知,终身学习践行者。关注后回复「Java」、「Python」、「C++」、「大数据」、「机器学习」、「算法」、「AI」、「Android」、「前端」、「iOS」、「考研」、「BAT」、「校招」、「笔试」、「面试」、「面经」、「计算机基础」、「LeetCode」 等关键字可以获取对应的免费学习资料。