运输层概述
概念
进程之间的通信
-
从通信和信息处理的角度看,运输层向它上面的应用层提供通信服务,它属于面向通信部分的最高层,同时也是用户功能中的最低层。
-
当网络的边缘部分中的两个主机使用网络的核心部分的功能进行端到端的通信时,只有位于网络边缘部分的主机的协议栈才有运输层,而网络核心部分中的路由器在转发分组时都只用到三层(到网络层)的功能。
进程之间通信流程
以体系结构的角度来看应用进程,可以理解为:运输层直接为上层应用进程间的逻辑通信提供服务
“逻辑通信”是指运输层之间的通信好像是沿水平方向传送数据,但事实上,这两条数据并没有一条水平方向的物理连接,要传送的数据是沿着图中上下多次的虚线方向传送的。也就是说,运输层只需要关心我这层需要做什么,不需要去关心下面三层经历了什么。(依旧体现透明传输的概念)
进程Ap1与Ap4之间进行基于网络的通信,进程Ap2与Ap3之间进行基于网络的通信
在运输层使用不同的端口,来对应不同的应用进程
然后通过网络层及其下层来传输应用层报文
接收方的运输层通过不同的端口,将收到的应用层报文,交付给应用层中相应的应用进程
这里端口并不是指看得见、摸得着的物理端口,而是指用来区分不同应用进程的标识符
总结
端口号、复用、分用
为什么用端口号
主要就是区分应用程序的
发送方的复用和接收方的分用
不同的应用进程按需求以不同的协议复用方式对数据进行封装,在复用(封装)时如果是UDP的话协议字段就是17,TCP的话协议字段就是6
接收方接到了之后,根据协议字段的代号进行IP分用,如果协议字段=17,就分用上报给UDP。如果协议字段=6,就分用上报给TCP。
分用完毕根据不同的端口号分发给需要的应用进程。
发送方的多个进程(这里一个端口表示一个进程) 可以使用同一个运输层协议(或者称为运输层接口)发送数据称为 复用。
接收方的多个进程(这里一个端口表示一个进程) 可以使用同一个运输层协议(或者称为运输层接口)接收时叫做 分用。
对于运输层端口号的分配,可以分为下面三类
TCP/IP体系的应用层常用协议所使用的运输层熟知端口号
常见的利用TCP/UDP来实现的各种应用层协议(顶层应用)
运输层传输流程
举例说明运输层端口号的作用:
在浏览器输入域名,回车浏览
然后用户PC中的DNS客户端进程会发送一个DNS查询请求报文
DNS查询请求报文需要使用运输层的UDP协议
首部中的源端口字段的值,在短暂端口号49151~65535中挑选一个未被占用的,用来表示DNS客户端进程
首部中的目的端口字段的值:53,是DNS服务器端进程所使用的熟知端口号
之后,将UDP用户数据报封装在IP数据报中,通过以太网发送给DNS服务器
DNS服务器收到该IP数据报后,从中解封出UDP用户数据报
UDP首部中的目的端口号为53,这表明应将该UDP用户数据报的数据载荷部分,也就是DNS查询请求报文,交付给本服务器中的DNS服务器端进程
DNS服务器端进程解析DNS查询请求报文的内容,然后按其要求查找对应的IP地址
之后,会给用户PC发送DNS响应报文,DNS响应报文需要使用运输层的UDP协议封装成UDP用户数据报
其首部中的源端口字段的值设置为熟知端口号53,表明这是DNS服务器端进程所发送的UDP用户数据报,目的端口的值设置为49152,这是之前用户PC中发送DNS查询请求报文的DNS客户端进程所使用的短暂端口号
将UDP用户数据报封装在IP数据报中,通过以太网发送给用户PC
用户PC收到该数据报后,从中解封出UDP用户数据报
UDP首部中的目的端口号为49152,这表明应将该UDP用户数据报的数据载荷部分,也就是DNS响应报文,交付给用户PC中的DNS客户端进程
DNS客户端进程解析DNS响应报文的内容,就可知道自己之前所请求的Web服务器的域名对应的IP地址
现在用户PC中的HTTP客户端进程可以向Web服务器发送HTTP请求报文(和DNS发送和接收流程差不多)
发送给目的服务器
收到报文后,Web服务器解封TCP数据报,看到端口是80,会将数据报文的部分,交付给HTTP服务器端进程,解析报文内容后,按其要求处理,处理完毕后回传
回传结果
总结:
TCP和UDP对比
- UDP 和 TCP 是TCP/IP体系结构运输层中的两个重要协议
- 当运输层采用面向连接的 TCP 协议时,尽管下面的网络是不可靠的(只提供尽最大努力服务),但这种逻辑通信信道就相当于一条全双工的可靠信道。
- 当运输层采用无连接的 UDP 协议时,这种逻辑通信信道是一条不可靠信道。
可靠信道与不可靠信道
-
两个对等运输实体在通信时传送的数据单位叫作运输协议数据单元 TPDU (Transport Protocol Data Unit)。
-
TCP 传送的数据单位协议是 TCP 报文段(segment)。
-
UDP 传送的数据单位协议是 UDP 报文或用户数据报。
UDP是全双工吗?
所谓全双工,半双工,单工是指面向连接时才有的说法,如果不是面向连接的,没有一个确定的连接的话,怎么会出现半双工这种只准一个来或者一个去的说法呢?
UDP支持一对一,一对多,多对一和多对多的交互通信。如果一定要涉及到全双工的话,大概理解为不仅提供全双工,甚至提供全多工服务,只是UDP是不可靠的服务而已。 因为UDP连一个稳定的连接都不需要建立!!!发送完报文就完事了,至于发送完后续,UDP不需要关心。
用户数据报协议UDP(User Datagram Protocol)
UDP概述
首先,UDP只是在IP的数据报服务上增加了有限的功能,主要是端口的功能(有了端口,运输层就能进行复用和分用)和差错检测的功能。
UDP的特点:
(1)UDP是无连接的,即发送数据之前不需要建立连接(发送数据结束时当然也没有连接可释放),因此减少了开销和发送数据之前的时延。
(2)UDP使用尽最大努力交付,即不保证可靠交付,同时也不使用流量控制和拥塞控制,因此主机不需要维持具有许多参数的、复杂的连接状态表。
(3)由于UDP没有拥塞控制,因此网络出现的拥塞不会使源主机的发送速率降低。这对某些实时应用是很重要的。很多实时应用(如IP电话、实时视频会议等)要求源主机以恒定的速率发送数据,并且允许在网络发生拥塞时丢失一些数据,但不允许数据有太大的时延。UDP正好适合这种要求。
(4)UDP是面向报文的。这就是说,UDP不再将应用程序交下来的报文划分为若干个分组来发送,也不把收到的若干个报文合并后再交付应用程序。应用程序交给UDP一个报文,UDP就发送这个报文。而UDP收到一个报文,就把它交付应用程序。因此,应用程序必须选择合适大小的报文。若报文太长,UDP把它交给IP层后,IP层在传送时可能要进行分片,这会降低IP层的效率。反之,若报文太短,UDP把它交给IP层后,会使IP数据报的首部相对太大,这也降低了IP层的效率。
(5)UDP支持一对一、一对多、多对一和多对多的交互通信。
(6)用户数据报只有8字节的首部开销,比TCP的20字节的首部要短得多。
二元组概念
UDP报文首部中最重要的字段就是源端口和目的端口,它们用来标识UDP发送方和接收方。
实际上,UDP通过二元组(目的IP地址,目的端口号)来定位一个接收方应用进程,而用二元组(源IP地址,源端口号)来标识一个发送方进程。
二元组(IP地址,端口号) 被称为套接字(Socket)地址。
在UDP中,因为是单双工的通信方式,所以不需要标记发送方的相关信息,只需要关注接收方地址+端口号即可
UDP报文到达接收方后,会根据目的端口号进行复用,如图为UDP多路复用模型
一个UDP端口与一个报文队列(缓存)关联,UDP根据目的端口号将到达的报文加到对应的队列中。
注意:这些到达的UDP报文可能来自于不同的发送方。但是无所谓,根据不同的端口号分发给对应的进程即可。
另外,由于UDP没有流量控制功能,如果报文到达的速度长期大于应用进程从队列中读取报文的速度,则会导致队列溢出和报文丢失。
UDP报文段格式
UDP报文段有两个部分:首部和数据部分。首部很简单,只有8字节,如由四个字段组成,每个字段都是2字节。
- 源端口:源端口号。
- 目的端口:目的端口号。
- 长度:UDP用户数据报的长度。
- 检验和:差错检验码,防止UDP用户数据报在传输中出错。
UDP校验和计算
UDP用户数据报首部中检验和的计算方法有些特殊。
在计算检验和时,要在UDP用户数据报之前增加12字节的伪首部。称其为“伪首部”是因为它并不是UDP用户数据报真正的首部,只是在计算检验和时,临时和UDP用户数据报连接在一起,得到一个临时的UDP用户数据报。检验和就是按照这个临时的UDP用户数据报来计算的。
伪首部既不向下传送也不向上递交,仅为计算检验和而存在,防止报文被意外地交付到错误的目的地。
Q:那这么看,伪首部存在的意义是什么?
A:因为伪首部里有UDP首部里没有的源IP和目的IP,这两个东西作为验证用的独一无二的key,计算出来的校验和也是独一无二的,这样就更保险了。
UDP首部为什么没有IP
不难发现,UDP首部没有IP地址,只有源端口和目的端口,光有端口号没有地址,怎么定位接收方?
这个很重要!!!
首先,UDP只是在运输层的协议,那么,只要想传输,UDP数据报需要借助IP数据报,才能发往目标主机。因此,在发送前,操作系统会把UDP报文作为数据封装进IP数据报中。而IP数据报是一定会有地址的。
在IP数据报中会标记好目的地址IP和源地址IP。那么这个IP从哪来?肯定不是从UDP里面来,因为UDP不含通信双方的IP地址,只有端口号。这个时候就要借助操作系统提供的Socket API,来让IP数据报获取目的IP地址。封装进IP数据报后,只要IP数据报能把数据送到目的主机,其目的就已经达到了。
到达目的主机后,剥离掉IP数据报的外壳,暴露出UDP报文,就可以根据UDP报文里的端口号,交付到对应的报文队列中。
Socket API向上连接了应用层,向下连接了网络层,可以提供各种信息
接收方发现错误的情况
端口号不正确
接收方UDP若发现收到的报文中的目的端口号不正确(即不存在对应于该端口号的应用进程),就丢弃该报文,并由网际控制报文协议(ICMP)发送一个终点不可达报文给发送方。
我们在网络层讨论ICMP的traceroute方法时,就是让发送的UDP用户数据报故意使用一个非法的UDP端口,结果ICMP就返回终点不可达报文,因而达到了测试的目的。
校验和不正确
UDP计算检验和的方法与IP数据报计算首部检验和的方法相似。不同的是,IP数据报的检验和只检验IP数据报的首部,但UDP的检验和把首部和数据部分一起检验。发送方先把全零放入检验和字段。再把伪首部及UDP用户数据报看成由许多16位的字串接起来。若UDP用户数据报的数据部分不是偶数个字节,则要填入一个全零字节(但此字节不发送)。然后按二进制反码计算出这些16位字的和。将此和的二进制反码写入检验和字段后,发送这样的UDP用户数据报。接收方把收到的UDP用户数据报和伪首部(及可能的填充全零字节)放在一起,按二进制反码求这些16位字的和。当无差错时其结果应为全1,否则就表明有差错出现,接收方就应丢弃这个UDP用户数据报(也可以上交应用层,附上出现了差错的警告)。这种简单的差错检验方法的检错能力并不强,但它的好处是简单,处理起来较快。
伪首部的第三字段是全零,第四字段是IP首部中的协议字段的值。以前已讲过,对于UDP,此协议字段值为17。第五字段是UDP用户数据报的长度。这样的检验和,既检查了UDP用户数据报的源端口号、目的端口号及UDP用户数据报的数据部分,又检查了IP数据报的源IP地址和目的地址。
传输控制协议TCP(Transmission Control Protocol)
概述
TCP是TCP/IP体系中面向连接的运输层协议,它提供全双工的和可靠交付的服务。TCP与UDP最大的区别就是,TCP是面向连接的,而UDP是无连接的。
也就是说,如果想用基于TCP协议传输数据,就需要收发双方提前建立连接。
TCP的主要特点
- TCP是面向连接的运输层协议。建立连接的目的是让通信双方为接下来的数据传送做好准备,初始化各种状态变量,分配缓存等资源。在传送数据完毕后,必须释放已建立的TCP连接,即释放相应的资源和变量。
- 每一条TCP连接只能有两个端点,即每一条TCP连接只能是点对点的(一对一)。TCP连接唯一地被通信两端的端点所确定,而每个端点由**二元组(IP地址,端口号)**唯一标识,即一条TCP连接由两个套接字(Socket)地址标识。因此每一个收发双方的连接,由两个二元组来定位。
- TCP提供可靠交付的服务。数据无差错、不丢失、不重复,并且按序到达。
- TCP提供全双工通信。连接成功后,允许收发双方任何时间收发消息。
- 面向字节流。TCP中的“流”(Stream)指的是流入进程或从进程流出的字节序列。面向字节流”的含义是,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序交下来的数据看成一连串的无结构的字节流。TCP不保证接收方应用程序所收到的数据块和发送方应用程序所发出的数据块具有对应大小。例如,发送方应用程序交给发送方的TCP共10个数据块,而接收方的应用程序是分4次(即4个数据块)从TCP接收方缓存中将数据读取完毕。但接收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样。
解读一下这个TCP收发示意图:
发送方的应用进程按照自己产生数据的规律,不断地把数据块(其长短可能各异,因为生成的数据肯定不是大小一致的)陆续写入TCP的发送缓存。
TCP再从发送缓存中取出一定数量的数据,将其组成TCP报文段(Segment)逐个传送给IP层,然后发送出去。图中表示的是在TCP连接上传送一个个TCP报文段,而没有画出IP层或链路层的动作。
接收方从IP层收到TCP报文段后,先把它暂存在接收缓存中,然后等待接收方的应用进程从接收缓存中将数据按顺序读取。
需要注意的是,接收方应用进程每次从接收缓存中读取数据时,是按应用进程指定的数量读取,而不是一次读取接收缓存中的一个完整的报文段或所有数据。只有当接收缓存中的数据量小于应用进程指定的读取量时,才读取接收缓存中所有的数据。当接收缓存中完全没有数据时,根据读取方式的不同,应用进程可能会一直等待,也可能直接返回。
由此可见,TCP的接收方应用进程读取的数据块的边界与发送方应用进程发送的数据块边界毫无关系,也就是说TCP接收方在向上层交付数据时不保证能保持发送方应用进程发送的数据块边界。
也就是说 接收方应用进程是按照自己的需求读取接收缓存,读多少全看应用进程心情,和发送方发来的数据长度的没有任何关系。(本身发送数据也变成字节流进入接收缓存了)
图上TCP连接是一条虚连接,而不是一条物理连接。也就是说,TCP连接是一种抽象的逻辑连接。
TCP报文段离开主机后直接进入物理链路
很重要的概念
:
TCP报文段首先要传送到IP层,加上IP首部后,再传送到数据链路层,再加上数据链路层的首部和尾部后,才离开主机发送到物理链路。 因为中间经由的设备,比如交换机,路由器,这两个设备都是处于三层及以下的协议,并不运行TCP/IP协议。自然不会有重组数据报的过程,路由器或者交换机也不会意识到有该连接的存在。
TCP连接主要包括通信两端主机上的缓存、状态变量。在这两台主机间的路由器和交换机没有为该连接分配任何缓存和变量。
TCP连接是四元组
与UDP的端口队列不同的是,TCP的发送缓存和接收缓存都是分配给一个连接的,而不是分配给一个端口的。
TCP的一个连接由四元组 [ 源IP地址,源端口号,目的IP地址,目的端口号 ] 标识,即由源/目的套接字地址对标识,也就是每一个四元组能确定一个TCP连接。
也就是说,来自不同源的TCP报文段,即使它们的目的IP地址和目的端口号相同,它们也不可能被交付到同一个TCP接收缓存中,因为它们的TCP连接不同,对应的收发双方缓存也不是同一套。 通常一个TCP服务器进程用一个端口号与不同的客户进程建立多个连接,然后创建多个子进程分别用这些连接与各自的客户进程进行通信。
TCP报文段格式
比UDP的结构要复杂的多
(1)源端口和目的端口:各占2字节。与UDP用户数据报一样,该字段定义了在主机中发送和接收该报文段的应用程序的端口号,用于运输层的复用和分用。IP和UDP一样,由Socket API提供。
(2)序号:占4字节。在一个TCP连接中传送的数据流中的每一个字节都按顺序编号。整个数据的起始序号在连接建立时设置。首部中的序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。接收方拿到序号后,根据序号来确认数据位于接收缓存的位置,方便应用进程定位读取。
(3)确认号:占4字节,是接收方期望收到的对方下一个报文段的第一个数据字节的序号。同时,确认号也代表这个序号以前的所有报文均已正常接收(TCP的累计确认)。TCP提供的是双向通信,一端在发送数据的同时对接收到的对端数据进行确认。4字节一共有32位,一共可以标记4G的数据,序号足够用了。
(4)数据偏移:占4位,它指出TCP报文段的数据部分起始处距离TCP报文段的起始处有多远。这实际上就是TCP报文段首部的长度。由于首部长度不固定(有可选字段),因此偏移是有必要的。
(5)保留:占6位,保留为今后使用,但目前应置为0。
下面有6个标志位说明本报文段的性质,它们的意义见(6)~(11)。
- (6)URG(URGent):URG=1表明紧急指针字段有效。表示紧急数据需要交付,高优先级。(比如命令行的Ctrl+C,高优先级终端操作)
- (7)ACK:只有当ACK=1时确认号字段才有效;当ACK=0时,确认号字段无效。
- (8)PSH(PuSH):出于效率的考虑,TCP可能会延迟发送数据或向应用程序延迟交付数据(发送缓存积攒一段时间),这样可以一次处理更多的数据。但是当两个应用进程进行交互式通信时,有时一端的应用进程希望在键入一个命令后立即收到对方的响应。这时,发送方TCP把PSH位置1,并立即创建一个报文段发送出去,而不需要积累足够多的数据再发送。同时,接收方拿到数据后,也会尽快向上层交付。
- (9)RST(ReSeT):RST=1表明TCP连接中出现严重差错(由于主机崩溃或其他原因),必须释放连接,然后重新建立运输连接。
- (10)同步SYN:用来建立一个连接。SYN=1而ACK=0,表明这是一个连接请求报文段。
- (11)终止FIN(FINal):用来释放一个连接。FIN=1表明此报文段的发送方的数据已发送完毕,要求释放运输连接。
(12)窗口:占2字节。窗口值指示该报文段接收方的接收窗口大小,为0到2的16次方-1,用来控制对方发送的数据量(从确认号开始,允许对方发送的数据量),单位为字节。窗口字段反映了接收方接收缓存的可用空间大小,计算机网络经常用接收方的接收能力的大小来控制发送方的数据发送量。
(13)检验和:占2字节。检验和字段检验的范围包括首部和数据部分。和UDP用户数据报一样,在计算检验和时需要加上伪首部进行计算,得出结果放入校验和。伪首部不参与传输。
(14)选项:长度可变。这里我们只介绍一种选项字段,即最大报文段长度(Maximum Segment Size,MSS),后面流量控制会用到。MSS告诉对方TCP:“我的缓存所能接收的报文段的数据部分的最大长度是MSS字节。”当没有使用该选项字段时,TCP的首部长度是20字节(即整个首部不包含可变部分,只包含固定部分)。
TCP的可靠传输
TCP通过以下几种措施,来实现可靠传输:
- 数据编号与确认
- 以字节为单位的滑动窗口
- 超时重传
- 快速重传
- 选择确认
数据编号与确认
前面我们知道,TCP是提供可靠传输,以及TCP传输是面向字节流的,那么由于网络数据包的到达顺序不一定是什么时候到达的,因此,需要对TCP数据报文进行编号操作。来避免失序问题。
TCP把应用层交下来的长报文(可能要划分为许多较短的报文段)看成一个个字节组成的数据流,并使每一个字节对应一个序号。
在连接建立时,双方TCP要各自确定初始序号。TCP每次发送的报文段的首部中的序号字段数值表示该报文段中首部后面的第一个数据字节的序号。
这么做的目的是为了防止伪造序号导致错误接收,因此初始序列号采用的是随机数
TCP使用的是累积确认,即确认是对所有按序接收到的数据的确认。但请注意,接收方返回的确认号是已按序收到的数据的最高序号加1,也就是说,确认号表示接收方 期望 下次收到的数据中的第一个数据字节的序号。例如,已经收到了1~700号、801~1000号和1201~1500号,而701~800号及1001~1200号的数据还没有收到,那么这时发送的确认号应为701。
TCP发送方发送报文后,会在自己的重传队列中存放这个发送报文段的副本,若成功确认,则删除副本,若到达超时时间未得到响应,则重传。
TCP的确认并不保证数据已交付应用进程,而只是表明接收方的TCP已按序正确收到了对方所发送的报文段。(运输层面保证送达)
TCP不会对每一个发送的报文都进行确认,也就是传送时采用捎带确认。
TCP采用了一种延迟确认的机制,即接收方在正确接收到数据时可能要等待一小段时间(一般不超过0.5s)再发送确认信息。若这段时间内有数据要发送给对方,则可以捎带确认。也有可能在这段时间内又有数据到达,则可以同时对这两次到达的数据进行累积确认。这样做可以减少发送完全不带数据的确认报文段,以提高TCP的传输效率。
接收方若收到有差错的报文段就丢弃(不发送否认信息);若收到重复的报文段,也要丢弃,但要立即发回确认信息。(督促需要的报文尽快重传)
这一点是非常重要的。若收到的报文段无差错,只是未按序号顺序到达,那么应如何处理?
多数TCP实现是先将其暂存于接收缓存内,待所缺序号的报文段收齐后再一起上交应用层。在互联网环境中,封装TCP报文段的IP数据报不一定是按序到达的,将失序的报文段先缓存起来可以避免不必要的重传。注意,不论采用哪种方法,接收方都要立即对已按序接收到的数据进行确认。
超时机制:
虽然每发送一个报文段就设置一个计时器在概念上最为清楚,但大多数TCP实现为了减少计时器开销,每个连接仅使用一个超时计时器。发送报文段时,若超时计时器未启动则启动它。收到确认时,若还有未被确认的报文段,则重启计时器。若超时计时器超时,仅重传最早未被确认的报文段(这一点和GBN超时就整个窗口重传有很大不同),并重启计时器。
以字节为单位的滑动窗口
为了提高报文段的传输效率,TCP采用滑动窗口协议 。但与GBN协议不同的是,TCP发送窗口大小的单位是字节,而不是分组数。TCP发送方已发送而未被确认的字节数不能超过发送窗口的大小。
如图所示为TCP中的窗口概念(假设发送窗口的大小为400字节)。落在发送窗口内的是允许发送的字节,发送窗口外左侧的是已发送并被确认的字节,发送窗口外右侧的是还不能发送的字节。收到确认信息后,发送窗口向右滑动,直到发送窗口的左沿正好包含确认号的字节。
滑动窗口流程:
图(a)表示发送窗口大小确定为400字节,初始序号为1,还没有发送任何字节,可以发送序号为1~400的字节。发送方只要收到了对方的确认信息,发送窗口就可前移。TCP发送方要维护一个指针。每发送一个报文段,指针就向前移动一个报文段的长度。当指针移动到发送窗口的右端(即窗口前沿)时,就不能再发送报文段了。
图(b)表示发送方已发送了400字节的数据,但只收到对前200字节数据的确认信息。由于窗口右移,现在发送方还可以发送200字节(401~600)。
图(c)表示发送方收到了对方对前400字节数据的确认信息,发送方最多可再发送400字节的数据(401~800)。
这个过程中,如果发送窗口的最左侧遇到了未确认的字节,就会被卡住不能右移,直到这个未确认的字节确认。
实际情况中,TCP的发送窗口是不断根据接收方接收窗口大小、网络拥塞状态
发送缓存 / 接收缓存
由于网络是个分布式的架构,每次分组的路由可能都不相同,因此不同的发送字节,未必会按序到达,对于不按序到达的分组,扔掉显然不合适。因此接收方缓存一般会采用放入缓存等待统一到达才向上交付。
注意:缓存是循环使用的,当正确的发送和接收一段时间后,就会自动删除完成 ACK/向上交付 数据,释放缓存空间
发送方缓存用来暂时存放:
- 发送方应用程序(应用层)传送给发送方TCP(传输层)准备发送的数据
- 发送方TCP已经发送,但是还未得到ACK的数据
发送窗口通常只是发送缓存的一部分。已ACK的数据应当从发送缓存中删除,因此发送缓存和发送窗口的后沿是重合的(窗口内多少数据待发送用序号差值算一下即可)。如果发送方应用程序传送给TCP发送方的速度太快,可能会最终导致发送缓存被填满,这时发送方应用程序必须等待,直到有数据从发送缓存中删除(才有空余空间给上层应用写入)。
接收方缓存用来暂时存放:
- 按序到达的,但尚未向上层应用交付的数据(上层未读取)
- 未按序到达,还不能向上层交付的数据(数据不整合,上层没法读取)
收到的分组如果被检测出有差错,则被丢弃。 如果接收方应用程序来不及读取收到的数据,接收缓存最终就会被填满,使接收窗口大小减小到零(同时反馈给发送方,将发送窗口减小到零)。 反之,如果接收方应用程序能够及时从接收缓存中读取收到的数据,接收窗口就会增大,但最大不超过接收缓存的大小。
超时重传时间的选择(比加权计算的平均RTTs略大一点)
RTO:超时重传时间
RTT + 数字:从发送方发出到接受方回应的往返时间,比如RTT0是第一个往返时间,RTT1是第二个往返时间
如果超时重传时间RTO的值设置得比往返时间RTT0的值小很多,这会引起报文段不必要的重传,使网络负荷增大。明明人家正常发送,提前重传肯定是浪费的
如果超时重传时间RTO的值设置得远大于RTT0的值,这会使重传时间推迟的太长,使网络的空闲时间增大,降低传输效率。
再假设RTO时间略大于RTT时间呢?好像对于固定的情况可以,但是又不太行,因为网络是不断在变化的,固定的RTO时间无法应对复杂的网络,RTT的时间会随着网络的占用状态而不断变化,所以固定的RTO时间肯定是不行的
这里给出公式,也就是每次的RTT时间进行加权平均,对整体数值进行平滑处理
简单讲一下:
每次在计算时,新的样本其RTT权重比较低,对于之前的RTT时间影响不大,这样就可以忽略掉或者说减小偶发性的拥塞对于RTT的影响,只有多次出现类似的情况后,才会对于RTT时间产生影响。
最后计算出来的,超时重传时间RTO是略大于加权平均往返时间RTTs的
RFC6298建议使用下式计算超时重传时间RTO
但往返时间RTT的测量比较复杂
假设有这样一种情况:发送方发送出一个报文段1。设定的超时重传时间到了,还没有收到确认,于是发送方重传此报文段。经过一段时间后,发送方收到了确认报文段。现在的问题:如何判定此确认报文段是对原来的报文段1的确认,还是对重传的报文段2的确认?由于重传的报文段2和原来的报文段1完全一样,因此源站在收到确认信息后,无法做出正确的判断,而正确的判断对确定RTTs的值非常重要。
这种情况下,就会有如下的问题:
那么既然怎么算都会错,我们就不利用新的往返时间来计算了,因为根本无法正确区分他们。卡恩(Karn)提出了一个算法:在计算RTTs时,不采用重传报文段的往返时间样本。
Karn算法优化,通过倍增RTO超时时间,来避免发送方超时,直到发送方不再发生超时时,才通过之前的算法来重新计算RTO时间,这样就避免了误确认无法区分导致真正的RTTs从而导致出现计算错误的问题。实践证明,这个办法也确实奏效。
快速重传(连续收到3个ACK,不用等RTO超时,立即重传)
超时触发重传存在的一个问题就是超时重传时间可能相对较长。由于无法精确估计实际的往返时间,RTO往往比实际的往返时间大很多。当一个报文段丢失时,发送方需要等待很长时间才能重传丢失的报文段,因而增加了端到端时延。幸运的是,有时一个报文段的丢失会引起发送方连续收到多个重复的确认信息,发送方通过收到多个重复的确认信息可以快速地判断报文段可能已经丢失,而不必等待超时计时器超时。快速重传就是基于该方法对超时触发重传的补充和改进。
当发送方一连收到三个重复的确认信息后,就知道可能是网络出现了拥塞,造成分组丢失,或是报文段M2虽未丢失但目前正滞留在网络中的某处,可能还要经过较长的时延才能到达接收方。快速重传算法规定,发送方只要一连收到三个重复的确认信息,就立即重传丢失的报文段M2(注意:重复确认的确认号正是要重传的报文段的序号),而不必继续等待为M2设置的超时计时器超时。因此可以看出,快速重传是对超时重传的补充而不是取代。
选择确认(精确到重传字节流中丢失的字节块)
我们知道TCP报文段的确认字段表示累积确认,就是说,它只通告收到的最后一个按序到达的字节,而没有通告所有收到的失序到达的字节,虽然这些字节已经被接收方接收并暂存在接收缓存中。
这些没有被确认的字节很可能因为超时而被发送方重传。一个可选的功能——选择确认(Selective ACK,SACK)(RFC 2018)可以用来解决这个问题。选择确认允许接收方通知发送方所有正确接收了但失序的字节块,发送方可以根据这些信息只重传那些接收方还没有收到的字节块,这很像前面介绍的选择重传(SR)协议的工作方式。
举例一个不连续接收方字节块:
很明显,这种情况下,光靠一个确认号,是无法将失序的字节块告诉发送方重传的。
这里每一块丢失的字节块,都需要两个边界的序号来表示(才能确定边界)
但TCP报文段的固定首部中没有哪个字段能提供上述这些字节块的边界信息,因此TCP在首部中提供了一个可变长的“SACK选项字段”来存放这些信息(可选字段)。
除此之外,要使用选择确认功能,在建立TCP连接时,双方还要分别在同步报文段和同步+确认报文段的首部中都添加 【允许SACK选项字段】 ,表示都支持选择确认功能。之后,才能在数据传输阶段使用SACK选项字段进行选择确认。(新出的功能,需要收发双方TCP版本都支持才能使用)
同样,选择重传协议,也是对累积确认功能的一种补充,并可以和使用累积确认的超时重传与快速重传机制一起工作。目前多数TCP实现都支持选择确认功能。
TCP的流量控制(接收方控制发送方的发送窗口大小)
出现原因、概念
TCP收发双方都有对应的发送、接收缓存。如果发送的太快,导致接收方来不及处理,最终导致接收缓存溢出,就会丢数据,造成效率下降。
TCP为应用程序提供了流量控制(Flow Control)服务,以解决发送方发送数据太快导致接收方来不及接收,使接收缓存溢出的问题。
流量控制的基本方法就是接收方根据自己的接收能力控制发送方的发送速率,因此也可以把流量控制看做一个速度匹配服务。
发送窗口大小在连接建立时由双方商定。但在通信的过程中,接收方会根据接收缓存中可用缓存的大小,随时动态地调整对方的发送窗口大小。在TCP报文段首部的窗口字段写入的数值就是当前接收方的接收窗口大小。 TCP发送方的发送窗口的大小必须小于该值。
TCP流量控制举例
以A主机作为发送方举例:
上图主机A现在可将发送缓存中序号1~200的字节数据全部删除,因为已经收到了主机B对它们的累计确认,删除后发送窗口右移。
这期间如果有数据没正确发送,或者没有收到正确接收的确认,等待时间一到就会触发重传计时器,进行重传操作。
当主机B发来累计确认的ACK后(代表期望下一个数据是501),A主机知道重传的201 ~ 300的数据已经ACK。此时即可删除发送缓存、根据ACK的数据调整发送窗口大小,并且右移发送窗口。
右移窗口后,发送方发送数据(这里假定每次能发送一百字节数据,发送窗口刚好可以一次性全发走)。在接收方B没有ACK之前,就不能继续发送。发送窗口在原地等待ACK。
当接收方B接收到数据之后,发现自己暂时没有接收能力了,ACK的同时,告知发送方发送窗口大小调节为0
以上为TCP流量控制的例子。
上面是TCP窗口逐渐变小的过程,如果是想发送窗口变大,就需要通知发送方扩大发送窗口。
但这个过程中,调整窗口变大的报文丢失了怎么办?难不成要一直等下去?
发送方有持续计时器,当知道接收窗口为0的时候,就会每隔一段时间问接收方是否可以接收了,这个询问的信息叫做零窗口探测报文,如果接收方还不能接收,就给发送方回复0大小的窗口信息,如果能接收了,就发送接收窗口调整的信息。
只要是发送窗口为0,这个零窗口探测报文,就会定期不停询问,直到接收方回复重新增大发送窗口。
即使零窗口探测报文在发送过程中如果丢失,还是能打破死锁局面,因为是周期性的发送
因为零窗口探测报文段也有重传计时器,重传计时器超时后,零窗口探测报文段会被重传
总结
TCP连接管理
熟悉的三次握手和四次挥手
三次握手 & 四次挥手简要图
三次握手
四次挥手
TCP建立连接(三次握手)
-
TCP 建立连接的过程叫做握手。
-
握手需要在客户和服务器之间交换三个 TCP 报文段。称之为三报文握手。
TCP的连接建立要解决以下三个问题
TCP使用“三报文握手”建立连接过程
- TCP 连接的建立采用客户服务器方式。
- 主动发起连接建立的应用进程叫做TCP客户 (client)。
- 被动等待连接建立的应用进程叫做TCP服务器 (server)。
连接过程
最初两端的TCP进程都处于关闭状态
说点废话:
一开始,TCP服务器进程首先创建传输控制块,用来存储TCP连接中的一些重要信息。例如TCP连接表、指向发送和接收缓存的指针、指向重传队列的指针,当前的发送和接收序号等
之后,就准备接受TCP客户端进程的连接请求
此时,TCP服务器进程就进入监听状态,等待TCP客户端进程的连接请求
TCP服务器进程是被动等待来自TCP客户端进程的连接请求,因此成为被动打开连接
TCP客户进程也是首先创建传输控制块
由于TCP连接建立是由TCP客户端主动发起的,因此称为主动打开连接
随后,在打算建立TCP连接时,TCP客户端进程 向 TCP服务器进程发送TCP连接请求报文段,并进入同步已发送状态
TCP连接请求报文段首部中
- 同步位SYN被设置为1,表明这是一个TCP连接请求报文
- 序号字段seq被设置了一个初始值x,作为TCP客户端进程所选择的初始序号
请注意:TCP规定SYN被设置为1的报文段不能携带数据,但要消耗掉一个序号。起始序号是由TCP随机生成的,这里随机生成是为了防止黑客劫持序号,伪造请求。
TCP服务器进程收到TCP连接请求报文段后,如果同意建立连接,则向TCP客户进程发送TCP连接请求确认报文段,并进入同步已接收状态
TCP连接请求确认报文段首部中
- 同步位SYN和确认为ACK都设置为1,表明这是一个TCP连接请求确认报文段
- 序号字段seq被设置了一个初始值y,作为TCP服务器进程所选择的初始序号,
- 确认号字段ack的值被设置成了x+1,这是对TCP客户进程所选择的初始序号 x 的确认,同时,期待收到下一个报文的序号seq是x+1
请注意:这个报文段也不能携带数据,因为它是SYN被设置为1的报文段,但同样要消耗掉一个序号
TCP客户进程收到TCP连接请求确认报文段后,客户端进程 还要向 TCP服务器进程发送一个普通的TCP确认报文段,并进入连接已连接状态
普通的TCP确认报文段首部中
- 确认位ACK被设置为1,表明这是一个普通的TCP确认报文段
- 序号字段seq被设置为x+1,这是因为TCP客户进程发送的第一个TCP报文段的序号为x,所以TCP客户进程发送的第二个报文段的序号为x+1
- 确认号字段ack被设置为y+1,表示对TCP服务器进程所选择的初始序号的确认(上次seq=y报文的确认)
请注意:TCP规定普通的TCP确认报文段可以携带数据,但如果不携带数据,则不消耗序号
TCP服务器进程收到该确认报文段后也进入连接已建立状态
现在,TCP双方都进入了连接已建立状态,它们可以基于已建立好的TCP连接,进行可靠的数据传输
为什么一定是三次握手
为什么TCP客户进程最后还要发送一个普通的TCP确认报文段?能否使用“两报文握手”建立连接?
从请求连接丢失的角度看
下图实例是“两报文握手”
为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误
这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留(会迟到很久),导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会浪费掉一次连接的建立。如果反复出现这种现象,就会大量浪费资源。
所以三次握手并不多余,这是为了防止已失效的连接请求报文段突然又传送到了TCP服务器,因而导致资源浪费
从TCP序号确认的角度看
换一个角度看,三次握手也是为了TCP收发双方的序号都能得到确认。
相比于UDP,TCP的可靠性就是依靠序号来实现的。因此,收发双方的序号就都需要得到ack才可以体现出TCP的可靠性,如果两次握手,那么至多只有连接发起方的起始序列号能被确认, 另一方的序列号则得不到确认。而且这是在连接的建立阶段,被动连接方是否已经建立连接是无法得到确认的。
从TCP序号确认的角度看为什么要三次握手
总结
关于序号:接收方和发送方都各自维护自己的序号,ack的+1操作,真是对于seq的确认,并不会修改它的值。并且之前在TCP的可靠传输中说过,TCP的字节有编号。因此连接的 建立/释放 都会发送自己的seq。
TCP断开连接(四次挥手)
TCP的连接释放
- TCP 连接释放过程比较复杂。
- 通信的双方都可以在数据传输完毕后随时申请释放连接。
- TCP 连接释放过程是四报文握手。
TCP通过“四报文挥手”来释放连接
- TCP 连接的建立采用客户服务器方式。
- 主动发起连接建立的应用进程叫做TCP客户 (client)。
- 被动等待连接建立的应用进程叫做TCP服务器 (server)。
- 任何一方都可以在数据传送结束后发出连接释放的通知
现在TCP客户进程和TCP服务器进程都处于连接已建立状态
TCP客户进程的应用进程通知其主动关闭TCP连接
TCP客户进程会发送TCP连接释放报文段,并进入终止等待1状态
TCP连接释放报文段首部中
- 终止位FIN和确认为ACK的值都被设置为1,表明这是一个TCP连接释放报文段,同时也对之前收到的报文段(接着之前的seq)进行确认
- 序号seq字段的值设置为u,它等于TCP客户进程之前已传送过的数据的最后一个字节的序号加1
- 确认号ack字段的值设置为v,它等于TCP客户进程之前已收到的、数据的最后一个字节的序号加1
请注意:TCP规定终止位FIN等于1的报文段即使不携带数据,也要消耗掉一个序号
TCP服务器进程收到TCP连接释放报文段后,会发送一个普通的TCP确认报文段并进入关闭等待状态(等待服务器把剩余数据传送完)
普通的TCP确认报文段首部中
- 确认位ACK的值被设置为1,表明这是一个普通的TCP确认报文段
- 序号seq字段的值设置为v,它等于TCP服务器进程之前已传送过的数据的最后一个字节的序号加1,这也与之前收到的TCP连接释放报文段中的确认号匹配
- 确认号ack字段的值设置为u+1,这是对TCP连接释放报文段的确认
- 注意,这里没有FIN=1的字段,代表此时还有数据没传完,无法断开连接
此时进入终止等待1状态(服务器通知应用进程)
TCP服务器进程应该通知高层应用进程,TCP客户进程要断开与自己的TCP连接此时,从TCP客户进程(发送方)到TCP服务器进程(接收方)这个方向的连接就释放了。也就是发送方不再发送任何数据包了(确认数据除外)
这时的TCP连接属于半关闭状态,此时TCP客户进程已经没有数据要发送了
但如果TCP服务器进程(接收方)还有数据要发送,TCP客户(发送方)进程仍要接收,因为TCP的连接是全双工的
TCP客户进程收到TCP确认报文段后就进入终止等待2状态,等待TCP服务器进程发出的TCP连接释放报文段
若使用TCP服务器进程的应用进程已经没有数据要发送了,应用进程就通知其TCP服务器进程释放连接
由于TCP连接释放是由TCP客户进程主动发起的,因此TCP服务器进程对TCP连接的释放称为被动关闭连接
TCP服务器进程发送TCP连接释放报文段并进入最后确认状态
该报文段首部中
- 终止位FIN和确认位ACK的值都被设置为1,终止位FIN=1表明这是一个TCP连接释放报文段,同时ACK=1也代表对之前收到的报文段进行确认
- 序号seq字段的值为w,这是因为在半关闭状态下,TCP服务器进程可能又发送
- 确认号ack字段的值为u+1,这是对之前收到的TCP连接释放报文段的重复确认
TCP客户进程收到TCP连接释放报文段后,必须针对该报文段发送普通的TCP确认报文段,之后进入时间等待状态
该报文段首部中
- 确认为ACK的值被设置为1,表明这是一个普通的TCP确认报文段
- 序号seq字段的值设置为u+1,这是因为TCP客户进程之前发送的TCP连接释放报文段虽然不携带数据,但要消耗掉一个序号
- 确认号ack字段的值设置为w+1,这是对所收到的TCP连接释放报文段的确认
TCP服务器进程收到该报文段后就进入关闭状态,而TCP客户进程还要进过2MSL后才能进入关闭状态(后面解释为什么是2MSL)
Question:
TCP客户进程在发送完最后一个确认报文后,为什么不直接进入关闭状态?而是要进入时间等待状态?
因为要保证最后一个断开连接的确认报文的到达,避免最后一个确认报文的丢失后接收方重传断开的请求无人理睬。如果接收方一直没收到关闭报文,就会一直重传确认报文,导致接收方无法关闭
如果没有等待状态,直接关闭,一旦最后的ACK丢失,就无法知道自己的ACK丢失了,接收不到重传的服务器释放请求。如图:
因为时间等待状态以及处于该状态2MSL时长,可以确保TCP服务器进程可以收到最后一个TCP确认报文段而进入关闭状态
另外,TCP客户进程在发送完最后一个TCP确认报文段后,在经过2MSL时长,就可以使本次连接持续时间内所产生的所有报文段都从网络中消失,即使最后一个ACK丢失没到达服务端,也没关系。因为 服务端会有超时重传 。这样就可以使下一个新的TCP连接中,不会出现旧连接中的报文段
对于这个等待时长,还有说等待一个超时时间的版本,但不重要,重要的是最后的ACK后,需要等待一段时间,至少也要一个超时时间。这样即便客户端超时,服务器端因为先发的FIN断开连接,也会先于客户端ACK重传。
TCP保活计时器
TCP双方已经建立了连接,后来,TCP客户进程所在的主机突然出现了故障(直接宕机,无法回复怎么办?)
TCP服务器进程以后就不能再收到TCP客户进程发来的数据
因此,应当有措施使TCP服务器进程不要再白白等待下去
TCP拥塞控制
Q:为什么要有拥塞控制?
A:防止过多的数据注入网络,使网络中的路由器或链路不致过载。
拥塞的原因以及危害
横坐标是输入负载或网络负载,代表单位时间内输入网络的分组数目。纵坐标是吞吐量(Throughput),代表单位时间内从网络输出的数据量。
简单说就是网络负载如果没有拥塞控制,随着负载上升,路由器工作队列有限。当输入负载达到某一数值时,网络的吞吐量反而随输入负载的增大而下降,这时网络就进入了拥塞状态。当输入负载继续增大时,网络的吞吐量甚至有可能下降到零,即网络已无法工作。(造成死锁)
拥塞产生实际的例子:
路由器R1到R2的最大速度为100Mbits/s
不难发现,整个网络的瓶颈就在于R1到R2之间的链路
没有拥塞控制的情况下,而A和C都以链路最高速率100 Mbit/s持续地发送数据。由于A和C速率都是100 Mbit/s,A和C在R1和R2之间的共享链路上各自获得50 Mbit/s的带宽。
但由于R2到D的链路带宽只有10 Mbit/s,C在路由器R2会损失40 Mbit/s的带宽(C发送的分组在路由器R2排队等候向D转发时被丢弃了)。虽然路由器R2到B的链路带宽充足,但跟A没有关系,平分带宽的状态下,R2到B只能用50 Mbit/s的速率传送数据。网络所能达到的实际吞吐量只有60 Mbit/s。
从A主机到B只能有效利用50Mbit/s
从C主机到D只能有效利用10Mbit/s
合计60 Mbit/s
这就出现了网络拥塞所带来的典型问题,即网络性能变差,资源被浪费。出现该问题的本质原因是,C有大量分组在路由器R2处因网络拥塞被丢弃,这些分组不能到达目的地,却白白占用了其所经过链路的资源。
Q:那么傻瓜式的增加R2的工作队列缓存能不能解决问题呢?
A:答案是不能,这只能解决突发的数据峰值,长时间看,由于链路速度跟不上,这个增大的工作队列迟早还是会被填满。
更糟的是,由于缓存变大,但链路带宽没变大(或者发送拥塞问题没有缓解),会导致大量的分组停滞在缓存中,这种长时间的滞留,会导致发送方C触发重传机制,重新发送分组。导致链路积压更多的分组,最后拥塞情况更加严重。
如果R2和D间的链路带宽增大到100 Mbit/s,则可以解决该问题。这似乎是找到了解决问题的关键,但实际上的网络比这要复杂得多,不可能像例子一样这么简单。但简单来说,既然网络拥塞是因为发送到网络中的数据量超过了网络的容量,要彻底解决分组交换网中的拥塞问题,就要想办法限制输入负载,即控制源点的发送速率。
拥塞控制的基本方法
拥塞控制是站在全局的角度,防止源点过多的数据注入网络,使网络能够承受现有的网络负载
流量控制是站在收发双方的角度,只关注收发双方的状态,不关心全局网络的状态
网络拥塞往往是由许多因素引起的。例如:
- 点缓存的容量太小;
- 链路的容量不足;
- 处理机处理的速率太慢;
- 拥塞本身会进一步加剧拥塞;
监测网络的拥塞主要指标有:
- 由于缺少缓存空间而被丢弃的分组的百分数;
- 平均队列长度;
- 超时重传的分组数;
- 平均分组时延;
- 分组时延的标准差,等等。
上述这些指标的上升都标志着拥塞的增长。
开环控制和闭环控制
关于两种拥塞控制的选择:
当网络系统的流量特征可以准确规定、性能要求可以事先获得时,适于使用开环控制;
而当流量特征不能准确描述或者当系统不提供资源预留时,适于使用闭环控制。
由于互联网不提供资源预留机制,而且流量的特性不能准确描述,所以在互联网中拥塞控制主要采用闭环控制方法。
闭环控制算法分为显式反馈算法和隐式反馈算法
- 显式反馈算法:ICMP源点抑制报文(这种报文会告诉发送方降速,但是如果网络拥塞了,往链路里发送ICMP报文会加重拥塞)
- 隐式反馈算法:源点通过对网络行为的观察(如分组丢失与往返时间RTT)来推断网络是否发生了拥塞,无须拥塞点提供显式反馈信息。TCP采用的就是隐式反馈算法。
TCP的拥塞控制实现
TCP采用的方法是让每一个发送方根据所感知到的网络拥塞程度,来限制其向连接发送数据的速率。TCP发送方如果感知从它到目的地的路径上没有拥塞,则提高发送速率(充分利用可用带宽);该发送方如果感知该路径上有拥塞,则降低发送速率。
TCP如何进行的拥塞控制? 为了进行流量控制,TCP的发送方会维持一个叫作接收方窗口(Receiver Window)的状态变量rwnd来记录接收到的TCP报文段首部中窗口字段的值(接收方通告的接收窗口大小),并通过该变量限制发送窗口的大小,来实现流量控制。另外,为了进行拥塞控制,TCP的发送方还维持了一个叫作拥塞窗口(Congestion Window)的状态变量cwnd,其大小取决于网络动态变化的拥塞程度。TCP发送方在确定发送报文段的速率时,既要考虑接收方的接收能力,又要从全局考虑不要使网络发生拥塞。因此TCP发送方的发送窗口大小取接收方窗口和拥塞窗口中的较小值
真正的发送窗口上限 = Min (接收方接收窗口大小,发送方拥塞窗口大小)
TCP发送方又是如何知道网络发生了拥塞呢? 我们知道,当网络发生拥塞时,路由器就要丢弃分组。现在通信线路的传输质量一般都很好,因传输出差错而丢弃分组的概率是很小的(远小于1%)。因此检测到分组丢失就可以认为网络出现了拥塞。(比如出现了快速重传和超时重传)
当拥塞发生后,就可以用后面介绍的算法来控制发送窗口大小,进而限制发送的速度。
慢开始和拥塞避免
慢开始(slow-start)
- 目的:用来确定网络的负载能力或拥塞程度。
- 算法的思路:开始时由小到大逐渐增大拥塞窗口数值。如果主机很多,而不慢启动,就会导致瞬时负载极大。因此要慢开始,逐渐增大发送速度。
- 两个变量:
- 拥塞窗口(cwnd):初始拥塞窗口值:不同标准,有不同的大小窗口初始值。
- 1 至 2 个最大报文段 (旧标准)
- 2 至 4 个最大报文段 (RFC 5681)
- 慢开始门限(ssthresh):防止拥塞窗口增长过大引起网络拥塞,也就是上限。
- 拥塞窗口(cwnd):初始拥塞窗口值:不同标准,有不同的大小窗口初始值。
如图所示拥塞窗口随着传播轮次(一个数据发送往返)的增长,发送窗口以指数级快速增大。
拥塞窗口从1开始,完成一次完整的发送接收,就进行一次指数级扩张。这个拥塞窗口的增长一直增长到慢开始门限(ssthresh)所规定的大小,不会一直无脑扩张下去,增大到慢开始门限,就要减缓增大速度。
在不清楚网络实际负载的情况下,慢开始算法可以避免新的连接突然向网络注入大量分组而导致网络拥塞。这对防止网络出现拥塞是个非常有力的措施。快速增长发送速率的目的是使发送方能迅速获得合适的发送速率。
图中swnd是发送窗口,在慢启动阶段发送速率以指数方式迅速增长,若发送速率持续以该速度增长,则网络必然很快进入拥塞状态。因此当网络接近拥塞时,应降低发送速率的增长速率。这可以使TCP连接在一段相对长的时间内保持较高的发送速率但又不致网络拥塞。当到达慢开始临界值后,引出拥塞避免算法。
拥塞避免(congestion avoidance)
- 思路:当到达慢开始门限后,让拥塞窗口 cwnd 缓慢地增大,从而避免出现拥塞。
- 每经过一个传输轮次,拥塞窗口 cwnd = cwnd + 1。
- 使拥塞窗口 cwnd 按线性规律缓慢增长。
- 在拥塞避免阶段,具有 “加法增大” (Additive Increase) 的特点。
当拥塞窗口增长大小到慢开始门限(ssthresh)的时候,每个传播轮次就是+1的上升
假设增长到某一时刻,发生了拥塞:
假设增长到24之后,如果此时在发送过程中出现部分报文段丢失,这必然会造成发送方对这些丢失报文段的超时重传,认为网络中出现了拥塞情况,就要开始减少发送量,直接把拥塞窗口降为1。重启慢开始流程。
注意,有的时候报文丢失并非是拥塞导致,这就很容易导致“误伤”。所以后面会有算法对这个问题进行改进。
这个时候又回到了慢开始,从拥塞窗口=1开始指数级增长,直至重新增长到慢开始门限(ssthresh)。再次进入拥塞避免算法阶段,开始+1级别上升。
两个算法完整示意图
慢开始+拥塞避免 配合
“慢开始”是指一开始向网络注入的报文段少,并不是指拥塞窗口cwnd增长速度慢;
“拥塞避免”并非指完全能够避免拥塞,而是指在拥塞避免阶段将拥塞窗口控制为按线性规律增长 使网络比较不容易出现拥塞;
快重传和快恢复
快重传和快恢复的出现是为了对慢开始和拥塞避免的算法进行优化处理。
有的时候报文丢失并非是拥塞导致,这就很容易导致“误伤”,造成的拥塞避免会使性能大幅下降。
分组丢失发生拥塞可以按照两种情况来讨论
- 能收到3个相同序号ACK,代表拥塞不严重,链路内还能有一定的交付能力。此时应该选择快速恢复算法
- 超时计时器触发,代表拥塞严重,链路已没有多余的交付能力,此时应该选择慢启动算法
快重传(fast retrasmit)
发现丢失了之后,接收方就会快速发送三个确认报文,来刺激发送方尽快重传。这样时间上,就早于计时器超时,一定程度上减少了拥塞避免的触发,所以提升了性能。快速重传需要配套快恢复使用。当快重传触发时,认为网络有一定交付能力,此时不触发慢开始算法(恢复时间太长)。而是触发快恢复
快恢复(fast recovery)
三个连续的确认到达发送方,就代表了当前网络还不算拥塞,该算法核心在于,触发后将拥塞窗口设置为当前发送窗口大小的一半,这样在恢复时的起点更高。利用快重传+快恢复
在收到连续3个重复的ACK时,可以合理的保证性能,不至于一下子把速度降低到0再慢慢恢复。
当然,如果是出现了超时重传事件,显然网络出现了拥塞,那就肯定要重走慢开始流程。这样可以有效的迅速减少从主机进入到网络中的分组数,使发生拥塞的路由器有足够的时间把积压的分组处理完毕
改进后的整体算法的示意图
在上述几种拥塞控制算法的加持下,对于长时间的TCP连接,稳定时拥塞窗口大小呈锯齿状变化。