《计算机网络 自顶向下方法》笔记:第三章 传输层
🚥计算机网络 自顶向下方法 系列文章导航🚥
一、概述和传输层服务
1.传输层提供的服务
传输层为运行在不同主机上的应用进程提供逻辑通信。
传输层的协议运行在端系统(而不是路由器):
-
发送方将应用层的报文分成报文段,然后穿递给下一层的网络层。
-
接收方从网络层接收后将报文段重组成报文,然后传递给上一层的应用层。
注意:本地进程间通信无需通过网络层和传输层,它们可以使用操作系统内核提供的进程间通信(IPC)机制进行直接通信。
2.传输层和网络层的比较
网络层服务:主机之间的逻辑通信
传输层服务:进程之间的逻辑通信
传输层运行在网络层的上层,将服务细分为主机间的进程通信
传输层在网络层的服务基础上对其进行加强。例如,解决网络层的数据丢失、顺序混乱等问题,但并不是所有的服务都能得到加强,比如带宽、延迟。
3.传输层协议
可靠的、保序的传输:TCP
-
多路复用、解复用
-
拥塞控制
-
流量控制
-
建立连接
不可靠的、不保序的传输:UDP
-
多路复用、解复用
-
没有为尽力而为的(best-effort) IP服务添加更多的其他额外服务
TCP和UDP均不提供的服务:延时保证、带宽保证
二、多路复用和解复用
1.定义
多路复用(Multiplexing) 指主机上的多个进程可以同时发送数据,通过传输层将它们的数据合并在一起并发送出去。传输层会为每个进程的数据流分配一个唯一的标识符,即端口号,传输层通过这些端口号区分各个数据流。
解复用(Demultiplexing) 指传输层在接收到数据包时,根据数据包中的端口号,将数据包交给对应的进程。每个接收到的数据包包含了源端口号和目的端口号,传输层通过目的端口号确定哪个进程应该接收该数据包。
2.多路复用和解复用过程
发送端:
-
应用进程生成数据,并通过系统调用传递给传输层。
-
传输层根据应用进程的端口号,为数据包加上传输层头部,头部里包含端口号信息以及其他内容,执行多路复用,将数据发送到网络中。
接收端:
-
传输层从网络中接收数据包,读取每个数据包上的目标端口号。
-
传输层在接收端通过读取目的端口号来识别目标应用进程,执行解复用,将去除传输层头部后的数据部分传递给相应的应用进程。
三、无连接传输UDP
UDP的全称为用户数据报协议(User Datagram Protocol)
1.UDP提供的服务
尽力而为的服务,数据报可能丢失、乱序;
无连接,UDP发送端和接收端之间没有握手,每个UDP数据报都被独立地处理;
2.UDP的应用
UDP被广泛应用于:
-
流媒体:丢失不敏感,速率敏感、应用可控制传输速率。
-
DNS:DNS用于将域名转换为IP地址。DNS查询通常使用UDP,因为DNS请求和响应通常较小且对延迟非常敏感。UDP的低开销和快速响应适合这种短小且频繁的请求。
-
SNMP:SNMP全称简单网络管理协议(Simple Network Management Protocol)。用于网络设备的管理和监控。SNMP使用UDP,因为管理数据的传输要求简单且不需要连接管理的开销。
3.UDP数据报格式
UDP数据报分为UDP头部和应用数据部分,UDP头部共占八个字节,分为四个字段:源端口号、目的端口号、长度和检验和,每个字段占两个字节。
TCP、UDP、IP术语对比
TCP:数据单元称为 “报文段”(Segment)。TCP的报文段包括了TCP头部和数据部分。
UDP:数据单元称为 “数据报”(Datagram)。UDP的数据报包括了UDP头部和数据部分。
IP:IP层的基本数据单元称为 “数据报”(Datagram)。IP数据报包含了IP头部和应用层数据(也就是传输层的数据报或报文段)。
但在一些书籍或资料中,为了强调UDP在传输层的角色,可能仍会用“报文段”这个词。这主要是术语使用上的差异,基本上两者在UDP的语境下指的是相同的概念。
4.UDP检验和
目标:检测在被传输的数据报中的差错(如比特反转)
检验流程:
接收端将数据报(包括头部,但校验和字段除外)分成16比特的字段,最后一块如果不足16位(通常是8位)则用零补齐。然后把所有的字段进行相加,溢出的最高位进位进行回卷,即末尾加一。将得到的结果再与校验和相加(在这之前还未将头部中的校验和加进去),若得到的是全为1的16个比特,则通过校验(发送端生成校验和时,也是将所有数据通过这种方式相加,然后取反码得到校验和)。
实际上一句话概括就是把所有的16位字段加起来,判断结果是不是FFFF,上面讲这么麻烦只是因为把校验和作为一个特殊的字段单独拎出来讲了。
如果未通过检验则说明数据报存在差错,但通过检验也只能说明未检测到差错,不能证明接收到的数据报一定正确无误。
注意:虽然 UDP 提供差错检测 , 但它对差错恢复无能为力 。 UDP 的某种实现只是丢弃受损的报文段, 其他实现是将受损的报文段交给应用程序并给出警告 。
四、可靠数据传输的原理
1.构造可靠数据传输协议
(1)问题描述
rdt(Reliable Data Transfer,可靠数据传输) 问题在应用层、传输层和数据链路层都很重要,是网络top10的问题之一。
rdt协议的复杂性取决于信道(channel) 可靠程度。信道越可靠,rdt协议就越简单;信道越不可靠,rdt协议就会越复杂。
下图展示了rdt的服务模型与具体实现:
虽然数据传输是双向的(即双全工数据传输),但我们暂时只讨论单向数据传输,前者只是后者的综合。即便如此,控制信息的流动依然是双向的,因为发送信息后需要得到反馈信息。
后面我们将使用有限状态机(FSM) 来描述发送方和接收方。
有限状态机(FSM) 的全称是 "Finite State Machine", 是一种数学模型,用于描述有限数量的状态及其转移关系。
(2)rdt 1.0 :在可靠信道上的可靠数据传输
在rdt 1.0中,我们假设下层的信道是完全可靠的,没有比特出错和分组丢失等问题。
发送方和接收方的FSM:
可以看到,此时,发送方只是简单地将数据发送到下层通道,接收方则是直接从下层通道接收数据。
(3)rdt 2.0:经具有比特差错信道的可靠数据传输
在rdt 2.0中,我们假设下层的信道可能会出现分组中比特翻转的错误。
解决方案:采用差错控制编码进行差错检测
引入两个控制报文:
-
确认(ACK):接收方显式地告诉发送方分组已被正确接收
-
否定确认(NAK):接收方显式地告诉发送方分组发生了差错,当发送方收到NAK后,重传分组
发送方和接收方的FSM:
我们可以看到,此时的发送方有两个状态:一个是等待来自上层的调用,当收到来自上层的数据后,将数据发送出去,并进入下一个状态;另一个状态则是等待来自接收端的ACK和NAK,如果收到ACK,则说明数据被成功接收,发送方进入下一个状态,如果收到NAK则说明数据被成功接收,发送方重新发送数据。
这种一次只能发送一个数据包,并进行停止等待的行为我们称之为停止等待协议。
停止等待协议(Stop-and-Wait Protocol) 是一种简单的可靠数据传输协议,常用于确保发送方和接收方之间的数据传输能够按序进行且没有数据丢失。它是面向连接的协议,通常应用于点对点通信。在停止等待协议中,发送方一次只能发送一个数据包,并且在发送完这个数据包后,它必须等待接收方的确认(ACK,Acknowledgment)消息。在接收到确认后,发送方才会继续发送下一个数据包。
(4)rdt 2.1:发送方处理出错的ACK/NAK
在rdt 2.1中有一个致命的缺陷,那就是如果ACK/NAK在传输过程中发生比特翻转,将导致发送方不知道如何行动。因为发送方无从判断接收方的实际情况,如果重传则可能导致数据重复,如果不重传则可能导致死锁(或出错)。
解决方案:引入序号机制处理重复
发送方在每个分组中加入序号,如果ACK/NAK,则重新发送当前分组。接收方如果收到重复序号的分组,则将其丢弃。
机制探讨
发送方:
-
在分组中加入序号(只需0,1两个序号即可),一次只发送一个未经确认的分组
-
必须检测ACK/NAK是否出错,需要EDC
EDC 是 Error Detection Code(错误检测码) 的缩写。它是一种用于检测数据在传输过程中是否发生错误的技术。常见的 EDC 技术包括 校验和(Checksum)、循环冗余校验(CRC, Cyclic Redundancy Check)、奇偶校验等。在停止等待协议中,EDC 用于检测发送方发送的分组(packet)和接收方发送的确认(ACK)或否定确认(NAK)是否在传输过程中出现了错误。由于传输信道不可靠,数据可能在传输过程中发生损坏,因此需要 EDC 来确保分组和确认的正确性。
接收方:
-
必须检测接收到的分组是否是重复的
-
接收方的状态会指示希望接收到的分组的序号是0还是1
发送方和接收方的FSM的状态数量变成了两倍,因为发送方和接收方必须保持一个状态,即跟踪当前发送/接收的分组序列号是 0 还是 1。这就使得状态数量比之前没有使用序列号时翻倍。
rdt 2.1的对ACK/NAK出错的处理过程如下图所示:
注意:此时接收方并不知道发送方是否正确地接收到了ACK/NAK,因为只安排了确认,并没有安排确认的确认。后面我们将讲到如何解决这种无限嵌套的困境。
(5)rdt 2.2:无NAK的协议
rdt 2.2是在有比特差错信道上实现的一个无NAK的可靠数据传输协议。它与rdt 2.1的区别在于,接收方需要为ACK也加上编号(依旧只需0,1两个编号即可)。这样我们就可以用对前一个信息的正向确认来代替对当前信息的反向确认。
具体处理过程如下图所示,当接收方正确收到信息后,则发送针对当前信息的确认报文;若没能正确接收信息,则发送针对前一个信息的确认报文,发送方收到后便会重新发送当前信息:
rdt 2.2将确认信息减少一半,协议处理更加简单,为后面的一次发送多个数据单元奠定了基础。
(6)rdt 3.0:经具有比特差错的丢包信道的可靠数据传输
在rdt 3.0中,我们假定除了比特受损外,底层的信道还会丢包。
发送方发送信息后,如果信息在传递过程中丢失,会导致接收方一直等待这条信息,而发送方则一直等待接收方对这条信息的回应,从而产生死锁。
解决方案:引入超时重传机制
发送方将数据包发送给接收方,并同时启动一个定时器。如果在超时时间内没有收到 ACK,发送方假定数据包丢失或确认丢失,自动重传该数据包。然后再次启动定时器并继续等待确认。
超时重传机制下,发送方的FSM:
timeout时间的选定
超时时间必须合理设置。过短的超时时间可能导致频繁的重传,增加网络负载。过长的超时时间则会导致数据传输效率下降。超时时间通常根据网络的往返时间(RTT, Round Trip Time)来动态调整。发送方测量每个数据包发送到接收方并收到确认所花费的时间,根据该时间调整下一次超时定时器的值。
注意:虽然超时重传机制可能会产生重复发送的问题(如超时时间过短),但这个问题我们在rdt 2.1中已经解决,因此不用担心。
下面展示rdt 3.0在无丢包操作、分组丢失、ACK丢失、过早超时四种情况下的运行流程:
2.流水线可靠数据传输协议
(1)停止等待协议的性能分析
停止等待协议的定义在上面的rdt 2.0中已经给出,此处不在赘述。
下面我们假设在rdt 3.0协议中,已知链路带宽R为1Gbps,端到端传播延时T为15ms,分组大小L为1kB。
可以计算:
传输延时:
带宽利用率:
实际吞吐量:
可以看到,在停止等待协议中,对链路带宽的利用率极低,1Gbps的带宽,实际上仅能达到270kbps的吞吐量。为此,我们引入了流水线协议。
(2)流水线协议
流水线协议(Pipelining Protocol) 是一种提高网络数据传输效率的协议设计,通过允许发送方在等待前一个数据包的确认时继续发送更多的数据包,而不是像“停止等待协议”那样必须等到每个数据包确认后才能继续发送。
流水线协议提升数据传输效率的原理很好理解,这里不做深究。需要注意的是,采用流水线协议后,我们也面临着新的问题:
-
我们必须增加序号的范围,用多个bit来表示分组的序号,仅靠以前的 0,1两个序号已经无法满足需求。
-
协议的发送方和接收方两端需要缓存多个分组,发送方最低限度应当能缓冲那些已经发送但没有确认的分组,接收方或许也需要缓存那些已经正确接收了的分组。
所需序号范围和对缓冲的要求取决于数据传输协议如何处理丢失、损坏以及延时过大的分组。解决流水线的差错恢复有两种基本方法:回退N步(Go-Back-N,GBN)和选择重传(Selective Repeat,SR)
3.滑动窗口协议
(1)概念引入
在介绍回退N步协议和选择重传协议之前,我们先引入一个一般性的协议,即滑动窗口(slide window)协议。
新的概念:
-
发送缓冲区:内存中的一个区域,落入缓冲区的分组可以发送,用于存放已发送,但是没有得到确认的分组
-
发送窗口:发送缓冲区内容的一个范围,那些已发送但是未经确认分组的序号构成的空间,是发送缓冲区的子集
-
接收窗口:用于控制哪些分组可以接收,只有收到的分组序号落入接收窗口内才允许接收,若序号在接收窗口之外,则丢弃
(2)滑动窗口,回退N步和选择重传三种协议的区别
协议类型 | 发送窗口大小 | 接受窗口大小 | |
---|---|---|---|
停止等待协议 | 滑动窗口协议 | SW=1 | RW=1 |
流水线协议 | 回退N步协议 | SW>1 | RW=1 |
选择重传协议 | SW>1 | RW>1 |
(3)发送窗口的移动
发送窗口的移动类似于数据结构中的队列:
-
发送端每发送一个分组,发送窗口的前沿就向前移动一个单位,移动的极限是与后沿的距离不能超过发送缓冲区的大小。
-
发送端收到老分组的确认报文后,发送窗口的后延进行移动,移动的极限是不能超过前沿。
(4)接收窗口的移动和发送确认
当RW=1时:
接收窗口的尺寸为1时,接收端只能顺序接收,此时发送连续收到的最大分组的确认,我们将这种确认称为累计确认。此时,发送端接收到一个分组的确认后,可以认为,在这个分组之前的分组也都被正确接收了。
当RW>1时:
接收窗口的尺寸大于1时,接收端可以乱序接收(但交给上层的分组依旧要按照顺序)。当高序号的分组乱序到来时,接收端只缓存但不交付(因为要实现rdt,不允许失序),不滑动;当低序号的分组到来时,接收窗口才会移动。
因为接收端每收到一个分组就会发送那个分组的确认,所以此时的分组确认为单独确认(非累计确认)。发送端在接收到一个分组的确认后无法判断这个分组之前的分组是否被成功接收。
正常情况下的两个窗口互动流程是:发送端上面来了分组->发送窗口滑动->接收窗口滑动->发确认->发送窗口滑动...... 原动力是接收端源源不断地收到来自上层的分组。异常情况下(乱序或丢失),GBN协议和SR协议则有着两种不同的处理方式。
4.回退N步
在GBN协议中,由于接收窗口大小为1,只能顺序接收,因此会将收到的乱序分组(没能落入接收窗口的范围内)抛弃,并重新发送老分组的确认。
发送窗口收到来自老分组的重复确认后,后沿不会向前滑动。此时超时重传机制会让发送端将发送窗口里的所有分组重新发送出去。
5.选择重传
在SR协议中,由于接收窗口大小大于1,可以连续接收,因此每收到一个分组都会发送这个分组的确认。但只有收到最低序号的分组时,接收窗口才会移动。
发送方每发送一个分组都会启动一个与这个分组对应的定时器,如果超时,则将重新发送对应的分组,而不需要把后续的分组也重复发送。
6.GBN与SR的比较
(1)GBN与SR的异同点
协议类型 | 相同点 | 不同点 |
---|---|---|
GBN |
|
|
SR |
|
(2)GBN与SR的优缺点
协议类型 | 优点 | 缺点 |
---|---|---|
GBN | 简单,所需资源少 | 一旦出错,回退N步代价大 |
SR | 出错时,重传代价小 | 复杂,所需要资源多 |
(3)GBN与SR的适用范围
-
出错率低:适合GBN,出错非常罕见,没有必要使用复杂的SR,为罕见的事情做日常准备和复杂处理
-
链路容量大(延迟大、带宽大):适合SR而不是GBN,一点出错所付出的代价太大
五、面向连接的传输TCP
1.概述
TCP的全称为传输控制协议(Transmission Control Protocol)
TCP是面向连接的(Connection-oriented):
因为在一个应用进程可以开始向另一个应用进程发送数据之前,这两个进程必须先相互“握手”。
TCP是点对点的(point-to-point):
即在单个发送方与单个接收方之间的连接。对于一次发送操作中,从一个发送方将数据传给多个接收方的“多播”情况,对TCP来说是不可能的。
TCP提供的是双全工服务(full-duplex service):
如果一台主机上的进程A与另一台主机上的进程B存在一条TCP连接,那么应用层数据就可以在从进程B流向进程A的同时,也从进程A流向进程B。
TCP是面向字节流的(byte-oriented):
TCP将上一层交付的数据仅看作一串无结构的字节序列。
TCP是流水线协议(Pipelining Protocol):
TCP的拥塞控制和流量控制设置窗口大小(发送方在等待接收方确认之前,能够连续发送的数据量大小)。
TCP的数据传输流程:
客户进程用过套接字传递数据流,TCP将这些数据引导到该连接的发送缓存(send buffer)中。TCP会不时地从发送缓存中取出一块数据,数据块的大小取决于MSS。每一块用户数据被取出后都会被TCP配上一个TCP首部,从而形成完整的TCP报文段。这些报文段被下传给网络层,当TCP在另一端接收到一个报文段后,该报文段的数据就被放入该TCP连接的接收缓存中,应用程序从此缓存中读取数据流。
MSS 与 MUT 的区分
最大报文段长度(Maximum Segment Size,MSS) 是TCP数据包中可以承载的最大数据部分的大小,不包括 TCP 和 IP 头部的开销。
最大传输单元(Maximum Transmission Unit,MTU) 是数据链路层的数据帧数据部分最大长度,表示一次能够通过网络接口传输的最大数据包的总大小,包括了网络层的数据包(如IP数据包)的头部和数据部分,但不包括链路层的头部和尾部。
以太网和PPP链路层协议都具有1500字节的MTU,TCP和IP的首部长度通常各为20字节,因此MSS的典型值为1460字节。
2.TCP报文段结构
TCP报文段由首部字段和一个数据字段组成:
TCP报文段的首部主要由以下部分构成:
名称 | 大小 | 含义 |
---|---|---|
源端口字段 | 2字节 | 表示发送方进程使用的端口号 |
目的端口字段 | 2字节 | 表示接收方进程使用的端口号 |
序号字段 | 4字节 | TCP对要传输的字节流中每一个字节都按顺序进行编号。序号字段中存储的是当前报文段数据部分第一个字节的序号(也就是相对于字节流的偏移量) |
确认号字段 | 4字节 | 表示期望收到的下一部分数据的首字节序号(通常为已确认最后一个字节的下一个字节的编号) |
首部长度(也称数据偏移字段) | 4比特 | 当前报文段首部的长度,以4字节为单位。由于4比特能表达的最大十进制数为15,因此首部长度最大为15*4=60字节 |
保留字段 | 6比特 | 留作之后使用的字段,目前应置0 |
紧急位URG(URGent) | 1比特 | 当URG位为1时,代表此报文段中有紧急数据,需要尽快发送,此时紧急指针字段(见下文)有效 |
确认位ACK(ACKnowledgement) | 1比特 | 当ACK位为1时,确认号字段有效,否则无效。TCP规定,在建立连接后之,所有发送报文段中的ACK需置1(只有在建立连接的第一步和释放连接的第一步ACK不需要置1) |
推送位PSH(PuSH) | 1比特 | 由于TCP是面向字节流的,每一个报文段的数据部分可以看作是字节流的一部分,所以当一端收到了一个报文段后,并不一定需要直接交付给应用层,而是可以等待缓存填满了再向上交付。如果一个报文段的PSH位为1,接收方收到这个报文段后就会尽快将当前的数据交付给应用进程(即“推送”) |
重置位RST(ReSet,又称复位位) | 1比特 | 当RST位为1时,当前TCP连接必须立即释放,然后重新建立连接。这通常被用在TCP连接中出现严重错误(某一端的主机崩溃等)时,同时也可以用于拒绝一个非法的报文段或拒绝打开一个连接 |
同步位SYN(SYNchronization) | 1比特 | 当SYN位为1时,代表这是一个请求连接或接收连接报文 |
终止位FIN(FINish) | 1比特 | 当FIN位为1时,代表该报文的发送方请求释放单向的连接 |
窗口字段 | 2字节 | 指的是该报文段的发送方的接收窗口,单位为字节 |
校验和字段 | 2字节 | 计算方法基本与UDP一致,需要计算首部和数据两个部分 |
紧急指针字段 | 2字节 | 当URG位为1时此字段才有意义。表示本报文段中的紧急数据长度,以字节为单位。规定紧急数据都在报文段数据部分的最前面,因此可以通过该字段直接确定哪些是紧急数据 |
选项字段 | 长度可变 | 当不使用选项时,TCP首部长度是20字节 |
填充字段 | 长度可变 | 因为数据偏移字段以四字节为单位,所以首部长度必须是四字节的整数,不足的部分就通过填充字段补齐 |
TCP序号和确认号的使用
TCP序号表示的是当前报文段数据部分第一个字节的序号,序列号用于跟踪数据流的顺序和确保数据包的完整性,接收方通过序列号来重新排序数据包,以便正确重组原始数据流。;确认号表示期望收到的下一部分数据的首字节序号,代表确认号之前的数据(不包括确认号本身)已被成功接受。
以下图为例,主机A发送Seq = 42,ACK = 79表示当前发送的数据第一个字节的序号是42,并对79之前的数据进行确认,示意主机B发送79及以后的数据。主机B收到数据后,发送Seq = 79,ACK = 43表示当前发送的数据第一个字节序号是79,并对主机A发来的数据进行确认。由于主机A发来的数据大小只有一个字节,因此确认码为42 + 1 = 43。后面的步骤同理。
3.TCP重传超时间隔的设置
设置思路:
-
要比RTT时间长:但RTT时间是动态变化的
-
不能太短:太早超时会造成不必要的重传
-
不能太长:对报文段丢失反应太慢
设置流程:
测量值 SampleRTT:测量从报文段发出到收到确认的时间,如有重传则忽略此次测量。
移动平均值 EstimatedRTT = (1-α )EstimatedRTT + αSampleRTT (推荐值:α = 0.125)
因为RTT的值是动态变化的,因此我们使用指数加权平均移动的方式计算,过去的样本值的影响呈指数衰减,越新的测量值所占据的比重越大。
如果RTT变化的幅度越大,那么我们设置的超时时间也应该越大,为此我们还要引入安全边界时间。
DevRTT = (1-β)DevRTT + β|SampleRTT-EstimatedRTT|(推荐值:β = 0.25)
安全边界时间设置为四倍的DevRTT,RTT变化(方差)越大,安全边界时间越大。
因此,最终的重传超时间隔设置为:
TimeoutInterval = EstimatedRTT + 4*DevRTT
4.可靠数据传输
(1)TCP可靠数据传输的特点
TCP在IP不可靠的尽力而为服务之上创建了一种可靠数据传输服务(reliable data transfer service)。TCP的可靠数据传输是一种流水线协议,是GBN和SR两种协议的混合体。
-
TCP采用累计确认(类GBN)
-
TCP使用单个重传计时器(类GBN)
-
TCP触发超时重传时只重发那个最早的未确认的段(类SR)
(2)快速重传
TCP除了超时重传外,还有快速重传机制。当发送方累计接收了相同数据的3个冗余的ACK后,即使此时还未到达超时重传时间,发送发也会选择重传改数据,这就是快速重传(fast retransmit)。
产生TCP ACK的建议【RFC 5681】:
事件 | TCP接收方的动作 |
---|---|
具有所期望序号的按序报文段到达。所有在期望序号及以前的数据都已经被确认 | 延迟的ACK。对另一个按序报文段的到达最多等待500ms。如果下一个按序报文段在这个时间间隔内没有到达,则发送一个ACK |
具有所期望序号的按序报文段到达。另一个按序报文段等待ACK传输 | 立即发送单个累计ACK,以确认两个按序报文段 |
比期望序号大的失序报文段到达。检测出间隔 | 立即发送冗余ACK,指示下一个期待字节的序号(其为间隔的低端的序号) |
能部分或完全填充接收数据间隔的报文段到达 | 倘若该报文段起始于间隔的低端,则立即发送ACK |
表格上的内容理解起来可能有些困难,简单点来说就是一下四种情况:
-
当发送方收到报文段后暂时不发送ACK,而是等待最多500ms(相对于超时间隔很小),如果等待期间没有另一个按序报文到达,再发送ACK。这样的等待是为了尽可能减少ACK的数量,减轻负担。
-
如果等待期间有另一个按序报文到达,则发送单个累计ACK,一次性确认两个按序报文。
-
如果接收窗口收到比期望序号大的失序报文,不会滑动,而是会产生间隔。此时立即发送ACK,希望尽快收到序号为间隔最低端的报文,从而填充间隔(要注意发送ACK的契机不是产生间隔,而是收到比期望序号大的失序报文,即每有一个这样的报文到达,无论会不会产生新的间隔都会发送一个ACK)。
-
当接收窗口收到能填充数据间隔的报文段时,如果该报文段起始于间隔的低端,则接收窗口可以滑动,然后立即发送新的ACK。
快速重传的一个实例如下图所示:
(3)流量控制
TCP为应用程序提供了流量控制服务(flow-control service) 以消除发送方使接收方缓存溢出的可能性。
具体机制:
接收窗口 (rwnd):接收方根据其接收缓冲区的剩余空间,计算出能够接收的数据量,并将该数值放在TCP报文段头部的rwnd字段中,发送给发送方。
发送方行为:发送方根据接收到的rwnd值,调整接下来要发送的数据量。如果rwnd值较大,发送方可以加快数据发送速率;如果rwnd较小,发送方会减缓发送速率,甚至停止发送数据,直到接收窗口再次打开。
(4)连接管理
在正式交换数据之前,发送方和接收方需要先握手建立通信关系。
两次握手的的弊端:
-
产生半连接:客服端发起建立连接请求后,如果一段时间内没有收到服务器的回复会再次发送请求。可能会出现重复建立连接的情况,浪费服务器资源。
-
新老数据的混乱:客户端发出了一个连接请求,但由于网络问题,该请求在途中延迟到达。客户端可能已经超时放弃了连接,并重新发起了一个新的连接请求。此时,服务器接收到了旧的请求,认为客户端要建立连接,随后在通信中会产生数据的混乱和误处理。
TCP连接建立(三次握手)
首先服务器进入LISTEN状态,等待客户端的连接。
a. 客户端向服务器发送一个连接请求报文段,内容如下:
-
同步位SYN为1。
-
初始序号seq通常随机设置,记为x。
-
不携带任何数据,即报文段的数据部分为空。
虽然该报文段不携带数据,但是仍需要消耗掉一个序号。发送后,客户端进入SYN-SENT状态。
b. 服务器收到客户端发来的连接请求后,如果同意建立连接,则向客户端发送一个确认报文段,具体内容如下:
-
同步位SYN为1,确认位ACK为1。
-
因为TCP提供全双工通讯,服务器也可以向客户端发送数据,所以服务器也需要为自己随机设置一个初始序号seq,记为y。
-
确认号ack = x + 1(因为连接请求报文段需要占用一个序号)。
-
不携带任何数据,即报文段数据部分为空。
确认报文段也需要消耗一个序号。发送后,服务器进入SYN-RCVD状态。
c. 客户端收到服务器发来的确认报文段后,需要再向服务器发送确认报文段,内容如下:
-
确认位ACK为1。
-
序号seq = x + 1。
-
确认号ack = y + 1(因为服务器的确认报文段需要占用一个序号)。
-
可以携带数据,也可以不携带数据。
该报文段如果不携带数据,则不消耗序号。客户端发送完该报文段后,进入ESTABLISHED状态。当服务器接收到该报文段后,也进入ESTABLISHED状态。此时,连接建立成功,双方可以进行数据传输。
注意:初始序号x,y之所以都随机选择而不是固定值是为了尽可能避免接收到上次连接过程中滞留的数据,从而导致新老数据的混乱。
TCP连接释放(四次挥手)
当客户端或服务器没有数据要传输时,都可以选择释放连接(单向释放,另一方仍可以发送信息)。当双方都释放了连接后,TCP连接才会完全释放。
假设客户端首先想释放连接。
a. 客户端向服务器发送连接释放报文段,内容如下:
-
终止位FIN为1。
-
序号seq为客户端发送的最后一个字节的序号加1,设为x。
-
不携带任何数据,即报文段的数据部分为空。
虽然该报文段并不携带数据,但是仍然需要消耗掉一个序号。发送后,客户端就进入FIN-WAIT-1阶段。
b. 服务器收到客户端发来的连接释放报文段后,向客户端发送一个确认报文段,内容如下:
-
确认位ACK为1。
-
序号seq为服务器已经传送过的数据的最后一个字节的序号加1(图中未展示)。
-
确认号ack = x + 1(因为连接释放报文段需要占用一个序号)。
服务器发送完确认报文段后,进入CLOSE-WAIT状态。此时客户端到服务器这个方向连接被关闭了,但服务器任然能向客户端发送信息,即TCP处于半关闭状态。如果服务器仍有数据要向客户端发送,那么就继续发送数据。当客户端收到服务器发送的确认报文段后,进入FIN-WAIT-2状态。
c. 如果服务器已经没有要向客户端发送的数据,就与前两步类似,解除服务器到客户端方向的连接。
客户端收到服务器发送的连接释放报文后,会发送一个确认报文段,进入TIME-WAIT阶段。然后等待两倍的**最长报文段寿命(MSL,Maximum Segment Lifetime,指一个TCP报文段在网络中能存在的最长时间)**后,进入CLOSED状态,客户端可以建立下一个连接。当服务器收到客户端发送的确认报文段后,进入CLOSE阶段,此时服务器可以建立下一次连接,TCP连接释放完毕。
为什么要等待两倍的MSL?
为了确保最后的ACK报文可靠到达,否则可能会产生在第三次挥手后,客户端已经进入CLOSE状态,但其发送的确认报文丢失或错误的情况。这时即使服务器触发超时重传机制,但由于客户端已经CLOSE,无法回复,从而导致连接无法关闭,持续占用资源的情况。
六、拥塞控制原理
1.拥塞的场景
情景一:两条连接共享具有无限大缓存的单跳路由
上图中,主机A和B共享一条容量为R的链路,该路由器带有缓存,这里我们假设缓存无限大,即数据包不会被丢弃或丢失,而是会被缓存在路由器中。
当主机A和主机B的发送速率小于等于R/2时,链路的总容量未被完全利用,每个主机可以以其发送速率顺利发送数据到接收方。因此,在这种情况下,接收方的吞吐量等于发送速率,即发送方发送的所有数据都成功到达接收方。当超过R/2时,链路的容量达到上限,即便发送方以更高的速率发送数据,接收方的吞吐量也不会继续增长。
虽然从吞吐量的角度来看,越接近R/2越好,可实际上,当发送速率接近R/2时,路由器的缓存会不断累积数据包,因为发送方的速率几乎等于链路的服务能力,导致路由器的队列无法清空,数据包在路由器的输出队列中等待的时间变得越来越长,最终导致平均延迟接近无穷大。
情景二:两个发送方和一台具有有限缓存的路由器
第二种情况下,路由器缓存的容量是有限的,当分组到达已满的缓存时会被丢弃。此时发送方会触发超时重传,重传数据包会增加网络负担,并可能导致更高的延迟和进一步的缓存溢出问题。更严重的情况是,发送方过早重传未丢失的数据包,导致路由器转发重复数据包,会浪费带宽并降低有效吞吐量。
情景三:四个发送方和具有有限缓存的多台路由器及多跳路径
第三种情况下,A向C,B向D,C向A,D向B传递数据,每个均要经过两跳。在流量很大的情况下,路由器缓存会逐渐满,导致缓存溢出。以路由器R2为例,由于缓存有限,A-C连接与B-D连接必须竞争缓存资源。假如B-D连接的流量较大,A-C连接在R2上的吞吐量会受到严重影响。最终,路由器R2的缓存可能被B-D连接的数据包填满,A-C连接在R2上的吞吐量可能接近于零。
而每当一个数据包在第二跳路由器上被丢弃,第一跳路由器所做的转发工作变成了无用功。这导致上游的传输能力被严重浪费。
2.拥塞控制方法
我们根据网络层是否为运输层拥塞控制提供了显式帮助来将拥塞控制方法划分为以下两种:
-
端到端拥塞控制:网络层没有为运输层拥塞控制提供显式支持,即使网络中存在拥塞,端系统也必须通过对网络行为的观察(如分组丢失与时延)来推断之。TCP采用的便是这种方式,路由器的负担较轻。
-
网络辅助的拥塞控制:路由器向发送方提供关于网络中拥塞状态的显式反馈信息,这种反馈可以是简单地用一个比特来指明链路中的拥塞情况;也可以是在更复杂的情况下,如ATM可用比特率(Available Bite Rate,ABR)拥塞控制中,路由器显式地通知发送方它(路由器)能在输出链路上支持的最大主机发送速率。
拥塞控制与流量控制的区别
- 拥塞控制关注的是网络层面的整体负载,确保网络资源不会因过多的数据流量而耗尽。它基于网络中节点和链路的状态来调整数据传输速率,以防止网络过载。
- 流量控制关注的是发送端和接收端之间的数据流速率,确保发送方不会超出接收方的处理能力,从而避免接收方的缓冲区溢出。它通过接收方的反馈来调整发送速率,保证数据的稳定传输。
七、TCP拥塞
1.机制介绍
TCP的拥塞控制采用的是端到端拥塞控制的方式,主要聚焦于如何检测拥塞控制以及对速度的控制策略。
(1)拥塞感知
发送端主要通过两种方式判断是否拥塞:
-
如果某个段超时了(丢失事件),则判断为路由器缓冲区没有空间了,导致该段丢失,即发生拥塞。当然也有可能是因为该段在传输过程中出错,没有通过校验而被丢弃,从而对拥塞情况产生误判,但这种情况发生的概率较小。
-
重复收到某个段的三次冗余ACK,则判断为轻微拥塞。网络这时还能够进行一定程度的传输,拥塞但情况要比第一种好。
(2)速率控制方法
运行在发送方的TCP拥塞控制机制跟踪一个可动态调整的变量,拥塞窗口(congestion Window),表示为cwnd。它对一个TCP发送方能向网络中发送流量的速率进行了限制,在一个发送方中未被确认的数据量不会超过cwnd与rwnd(接收窗口)中的最小值,即
从这里我们可以看出,发送速率的调整是基于流量控制和拥塞控制的联合控制。
在前面流量控制中我们讨论了接受窗口rwnd,那么这里的拥塞窗口cwnd要怎么估计呢?
粗略地来讲,在每个往返时间RTT的起始点,上面的限制条件允许发送方向该连接发送cwnd个字节的数据,在该RTT结束时发送方接收对数据的确认报文。因此发送方发送的数据大概为cwnd/RTT 字节/秒。通过调节cwnd的值,发送方能够调整其发送数据的速率。虽然这种方式很不精确,但是由于有反馈,因此仍然能够让我们达到相关的目的。
cwnd的动态调整:
-
超时或收到3个重复ack,则cwnd减小
-
否则(正常收到ack,未发生以上情况),则cwnd增大
后面我们将具体地讲cwnd的调整规则。
2.TCP拥塞控制流程
(1)慢启动
当一条TCP连接开始时,cwnd的值通常被设为1(个MSS的大小)。这就使得初始发送速率cwnd/RTT的值很小。为了迅速最大化利用带宽,在慢启动(slow-start) 阶段,cwnd的值以一个MSS开始,并且每当传输的报文段首次被确认就增加一个MSS。在这个过程中,每经过一个RTT,发送速率就会翻倍。也就是说,TCP的发送速率起始很慢,但在慢启动阶段会以指数级增长(从宏观上来看,慢启动阶段的时间其实很短,慢启动并不“慢”)。
指数级增长的速率很快就会触碰到带宽的上限,那么什么时候停止这种增长呢?
出现超时:
当出现超时情况后,TCP发送方会把cwnd的大小设置为1并重新开始慢启动过程,同时将触发超时时,cwnd大小的一半记为警戒值,即ssthresh=cwnd/2。后面当cwnd到达或超过警戒值后,说明此时快要触碰到上限了,继续指数增长过于鲁莽,因此会结束慢启动阶段,进入拥塞避免阶段。在拥塞避免阶段,cwnd线性增长。当再次触发超时时,发送方会更新警戒值,将其设置为当前cwnd大小的一半,然后把cwnd的大小设置为1重新开始慢启动,以此类推。
出现三次冗余ack时:
由于出现三次冗余ack的情况并没有超时这么严重,因此我们不需要将cwnd设置为1重新慢启动,这时TCP执行一种快速重传并进入快速回复状态,后面将详细讨论这块内容。需要注意的是这种情况下,警戒值也会被更新为此时cwnd大小的一半。
(2)拥塞控制
一旦进入了拥塞控制阶段,cwnd的值大约是上次遇到拥塞时值的一半,说明距离拥塞已经不远。TCP选择采用了一种更保守的方式,即每个RTT只将cwnd增加一个MSS。后续触发超时,会更新警戒值并进入慢启动阶段;出现三次冗余ack则反应不会这么剧烈,只会在更新警戒值后,跳过慢启动,直接进入拥塞控制状态,这种行为我们称之为快速恢复。
(3)快速恢复
由三个冗余ack触发的丢包被视为轻度拥塞, 在快速恢复算法中,发送端会把cwnd设置为原来的一半再加上三个MSS。
这里的加三可能有些难以理解。我们先来思考产生冗余ack意味着什么。既然发送方收到了三个重复的确认,就表明有三个分组已经离开了网络。这三个分组不再消耗网络的资源,而是停留在接收方的缓存中(接收方发送出3个重复的确认就证明了这个事实)。可见现在网络中不是堆积了分组,而是减少了三个分组。因此可以适当把拥塞窗口扩大些,我们可以把它理解为一种补偿机制。
TCP拥塞控制的FSM描述见下图:
不同教材对快速恢复的解释:
在《计算机网络 自顶向下方法 第七版》这本书中,将快速恢复机制解释为把cwnd设置为原来的一半再加三;而在《计算机网络 第七版 谢希仁》中,则是将快速恢复机制解释为简单地把cwnd设置为原来的一半,但也提及了一下额外加三的情况。谢希仁书中的原话是:“请注意,也有的快速恢复实现是把快恢复开始时的拥塞窗口cwnd值再增大一下(增大3个报文段的长度),即等于新的ssthresh + 3 * MSS”。而在部分高校(如大连理工大学)的教学和一些教辅资料(如王道考研复习指导)中,也都是把cwnd简单地设置为原来的一半。
由于本笔记以《自定向下方法》这本书的内容为准,因此这里为大家介绍的是额外加三个MSS的版本。
3.TCP吞吐量
我们一般用窗口window的尺寸W和RTT来描述TCP的平均吞吐量,在这个过程中,我们忽略慢启动阶段(因为从宏观上看,慢启动用时很短),并假设发送端总有数据传输。
从上图我们可以得到:
平均窗口尺寸:
平均吞吐量:
4.TCP的公平性
在本章第六部分的第1小节的情景一中我们引入了一种场景,即两条连接共享具有无限大缓存的单跳路由,这里我们忽略缓存的部分,只考虑主机A和B共享一条容量为R的链路。当时我们默认主机A和B的发送速率都是R/2。不知道当时你有没有好奇为什么两条连接的最终发送速率要相等呢?笔者当时也没搞明白,看到这一节才恍然大悟。
如果K个TCP会话分享一个链路带宽为R的瓶颈,每一个会话的有效带宽为R/K,我们将TCP的这种特点称之为TCP的公平性。
为什么TCP会有这种性质呢?让我们整理一下前面学到的拥塞控制的内容,然后观察下图:
我们首先假设两条连接最开始的状态位于A点,此时连接1的吞吐量大于连接2,由于A点位于RR的下方,说明两条连接都处于拥塞控制状态(忽略慢启动)。此时每过一个RTT,两条连接的cwnd都会加一,也就是说此时的状态会由A点45度向右上角移动。当到达B点后,由于B点位于RR上方,超过了带宽的承受能力,两条连接的吞吐量都会回退到原本来的一半(依旧忽略慢启动)。回退后会回到A点吗?当然不是。因为连接1的吞吐量比较大,减半后减少的吞吐量会更多,所以会退到C点,然后继续进入拥塞控制状态慢慢移动到D点。以此类推,最终会收敛到两条连接的吞吐量相等的位置。
当然,在实际情况中,这种公平性不是绝对的,如果两条连接的RTT不相等,那么RTT较短的那条连接的吞吐量增长速率显然会更快,从而占据更多的带宽。
UDP有很强的侵略性,如果和TCP公用一条链路,它会严重压制TCP的流量,因为UDP不需要考虑拥塞控制的问题,而TCP要考虑的就多了(doge)。