运输层
第一部分:概述
在学习完应用层的时候,对运输层就有了一定的认识。需要指出的是,运输层是在端系统中的而不是在路由器中的。在发送端系统中,运输层将从发送应用程序进程接收到的报文转换成运输层分组。实现的方法是将报文(Message)划分成为较小的块,并为每块加上一个运输层首部以生成运输层报文段(segment)。然后运输层将这些报文段传递给网络层,网络层将其封装成网络层分组·······在接受端,网络层从数据段中提取运输层报文段,并将该报文段向上交给运输层。接收方:把报文段重组成应用数据,交付给应用层
运输层和网络层的关系
网络层: 不同主机之间的逻辑通信
运输层: 应用进程之间的逻辑通信
网络层负责ip数据报的产生以及ip数据包在逻辑网络上的路由转发。
网络层只是根据网络地址将源结点发出的数据包传送到目的结点(点到点),其主要任务是:通过路由选择算法,为报文或分组通过通信子网选择最适当的路径。该层控制数据链路层与传输层之间的信息转发,建立、维持和终止网络的连接。具体地说,数据链路层的数据在这一层被转换为数据包,然后通过路径选择、分段组合、顺序、进/出路由等控制,将信息从一个网络设备传送到另一个网络设备。
传输层提供端到端通信服务层次,提供可靠及非可靠连接。
传输层则负责将数据可靠地传送到相应的端口(端到端),传输层提供了主机应用程序进程之间的端到端的服务。传输层利用网络层提供的服务,并通过传输层地址提供给高层用户传输数据的通信端口,使高层用户看到的只是在两个传输实体间的一条端到端的、可由用户控制和设定的、可靠的数据通路。
类似于家庭间通信:
12个孩子要与另一个家庭的12个孩子相互通信
- 进程 = 孩子们
- 进程间报文 = 信封中的信笺
- 主机 = 家庭的房子
- 运输协议 = 张三 和 李四(站在大门口的管家)
- 网络层协议 = 邮局提供的服务
上例中的几种特殊场景
1、张三和李四生病了,无法工作,换成张五和李六
【不同的运输层协议可能提供不一样的服务】
2、邮局不承诺信件送抵的最长时间
【运输层协议能够提供的服务受到底层网络协议的服务模型的限制】
3、邮局不承诺平信一定安全可靠的送达,可能在路上丢失,但张三、李四可在较长时间内没有受到对方的回信时,再次誊写信件,寄出
【在网络层不提供某些服务的情况下,运输层自己提供】
ps:
网络层为主机间提供逻辑通信, 而运输层为应用进程间提供端到端的通信逻辑.
网络层提供IP数据报首部中的检验和字段, 只检验首部是否出现差错; 而运输层要检验收到的报文是否有差错.
【邮局只负责送到家,东西坏没坏由管家去查看。管家觉得没问题再给主人(应用)】
第二部分:多路复用和多路分解
TCP和UDP的最基本任务
将主机到主机之间数据的交付(由IP层提供)扩展为运行在二个主机上的进程间的数据交付。
一台主机可以同时运行多个网络进程
发送方:多个进程通过各自端口将数据交付给运输层,共同使用运输层的服务。这叫运输层的多路复用。
接收方:当运输层收到从下层网络层传递上来的数据后,通过端口号就数据向上交付给各自的应用进程。这叫运输层的多路分解。
可以说多路分解和多路复用是运输层的核心作用。
端口
端口的作用就是让应用层的各种应用进程都能将其数据通过端口向下交付给运输层,以及让运输层知道应当将其报文段中的数据向上通过端口交付给应用层相应的进程(或者线程)
从这个意义上讲,端口是用来标志应用层的进程(或者线程)
端口用一个 16 bit 端口号进行标志
套接字
TCP 使用“连接”(而不仅仅是“端口”)作为最基本的抽象,同时将 TCP 连接的端点称为套接字(socket) 。
套接字和端口、IP 地址的关系是:
报文段(数据段)的投送过程
主机收到IP包
每个数据包都有源IP地址和目的IP地址
每个数据包都携带一个传输层的数据报文段
每个数据报文段都有源、目的端口号
主机根据“IP地址+端口号”将报文段定向到相应的套接字(这里注意的是,TCP和UDP的socket的内容有差异)
面向连接的复用和分用
TCP 套接字由一个四元组来标识
(源IP地址,源端口号,目的IP地址,目的端口号)
【提供源IP地址和源端口号是为了TCP连接时的握手】
接收方主机根据这四个值将报文段定向到相应的套接字(网络层只管送套接字)
服务器主机同时支持多个并发的TCP套接字:
【每一个套接字都由其四元组来标识】
Web服务器为每一个客户连接都产生不同的套接字
非持久HTTP对每一个请求都建立不同的套接字(由应用层可知会影响性能)
无连接的复用和分用
UDP 套接字由一个二元组来标识
(目的IP地址,目的端口号)
【无握手,故称为无连接】
接收方根据目的端口号将报文段定向到相应的套接字
具有不同源IP地址和/或源端口的UDP报文如果具有相同的目的IP地址和目的端口号,则定向到相同的套接字
第三部分:无连接的UDP
一个最简单的运输层协议必须提供
多路复用/多路分解服务
差错检查(虽然进行差错检测,但不进行差错恢复。只是丢弃出错的UDP报文或交给应用程序但发出警告)
请思考下为什么UDP提供的是无连接的不可靠的传输服务,却还要提供差错检测?
【这是为了防止一直出错而导致数据根本传递不了。不进行差错恢复不意味着可以让数据一直丢失】
几乎没有对IP增加什么东西。如果程序开发人员选择基于UDP的Socket,则应用程序几乎是直接与IP打交道。
UDP处理数据的流程
发送方
- 从应用进程得到数据
- 附加上为多路复用/多路分解所需的源和目的端口号及差错检测信息,形成报文段(数据报)
- 递交给网络层,尽力而为的交付给接收主机
接收方
- 从网络层接收报文段(数据报)
- 根据目的端口号,将数据交付给相应的应用进程
UDP的优势
- 无需建立连接——建立连接会增加时延
- 简单——发送方和接收方无需维护连接状态
- 段首部开销小——TCP:20Byte vs UDP:8Byte
- 无拥塞控制——UDP 可按需要随时发送
UDP大量应用可能导致的严重后果
路由器中大量的分组溢出
显著减小TCP通信的速率,甚至挤垮TCP会话
使用UDP的可靠数据传输
在应用层实现数据的可靠传输
增加了应用进程的实现难度
这点说明我们不能单纯的凭借主观感受来说UDP是一定不能可靠数据传输的。
UDP报文段(数据报datagram)
UDP的首部格式
源端口号以及目的端口号
用于唯一标识套接字,指示该报文段所要交付的套接字。端口号一般是16个bit,范围[0,65535]。其中0-1023是周知端口号,比如HTTP是80端口,FTP是21端口
长度
UDP用户数据报的长度(首部字段和数据字段),其最小值是8,也即是只有首部。
1 首部只有8个字节
源端口
: 需要对方回信时选用, 不选可全为0.目的端口
: 在终点交付报文时使用.长度
: UDP用户数据报的长度.检验和
: 检验UDP在传输中是否有错.
校验和
检测UDP用户数据报在传输的过程中是不是有错,有错就丢弃。
UDP的检查和
目标:检测收到的报文段的“差错” (例如, 出现突变的比特)
发送方
- 把报文段看作是16比特字的序列
- 检查和:对报文段的所有16比特字的和进行1的补运算
- 发送方将计算校验和的结果写入UDP校验和字段中
接收方
计算接收到的报文段的校验和
检查计算结果是否与收到报文段的校验和字段中的值相同
【不同 — 检测到错误
相同 — 没有检测到错误(但仍可能存在错误)】
UDP特点
- 无连接 : 发送数据之前不需要连接.
- 尽最大努力交付 : 不保证可靠交付.
- 面向报文 : 对应用层下发的报文, 添加d首部后就下发到IP层, 对下发的报文不合并也不c拆分, 仅仅保留报文的边界. 一次交付一个报文.
- 无拥塞控制 : 发送后就不在管理.
- 多种通信 : 支持一对一, 一对多, 多对多通信.
- 首部开销小 : 首部仅有8个字节.
第四部分:可靠数据传输(十分重要)
本节只讨论单向数据传输:数据是从发送端到接受端的。
在实际的网络传输中,信道是不可靠的,在其上传输的分组可能会损坏或丢失,甚至相对次序都不能保证。
在这种情况下,应用层的程序迫切需要运输层提供一个可靠的数据传输服务,可以保证无论在实际的物理传输中发生了什么,数据都可以无损按序地交付给接收端。这就是可靠数据传输协议的作用,也是 TCP 向调用它的应用所提供的服务模型。
信道 (channel):信道一般指连接信号发送方和接收方的传输线路,包括双绞铜线、同轴电缆、光纤、陆地无线电或者卫星无线电等物理媒体。
可靠信道上的可靠传输—— rdt 1.0
考虑的情况:底层信道完全可靠。即不会产生比特错误、不会丢失分组。而且也假定接收方接收数据的速率与发送端发送数据的速率一样快。
可以看到,rdt 的发送端只通过 rdt_send(data)
事件接收来自较高层的数据发送请求。在完成一次数据发送请求中需要两个动作:
- 产生一个包含该数据的分组(经由
make_pkt(data)
产生) - 然后将该分组通过
udt_send(packet)
发送到信道中
完成这两个动作后,重新返回原始状态,继续等待来自较高层的数据发送请求。
而在接收端,rdt 通过 rdt_rcv(packet)
事件从底层信道接收一个分组。在一次数据接收过程中同样需要两个动作:
- 从分组中取出数据(经由
extract(packet, data)
产生) - 然后将数据上传给较高层(通过
deliver_data(data)
动作)
和发送端一样,接收端完成这两个动作后也重新返回原始状态,继续等待从底层信道接收分组。
需要注意的是,在发送端,引起状态变迁的事件是由较高层应用的过程调用产生的;而在接收端,引起状态变迁的事件是由较低层协议的过程调用产生的。
现在我们就构造出了适用于可靠信道的可靠数据传输协议 rdt 1.0 ,因为信道可靠,接收方也不需要提供任何反馈信息给发送方,不必担心出现差错。而且因为假定了接收方接收数据的速率能够与发送方发送数据的速率一样快,所以接收方也没有必要请求发送方慢一点发送。
经具有比特差错信道的可靠数据传输协议 rdt 2.0(解决分组出错问题,通过ACK/NAK告知发送方)
考虑的情况:分组比特可能受损,所有传输的分组都将按序被接收,不会丢失
首先需要明确的一点是:如果发送方知道了哪些分组发送出去后接收方并没有收到,那么发送方就需要重传这些分组。基于这样的重传机制的可靠数据传输协议称为自动重传请求(Automatic Repeat Request, ARQ)协议 。
ARQ处理机制
如何判断分组受损——差错检测
【加校验和checksum】
如何通知发送方分组是否受损——接收方反馈(ACK和NAK)
确认——acknowledgements (ACKs): 接收方明确告诉发送方正确收到分组
否认——negative acknowledgements (NAKs): 接收方明确告诉发送方分组有错
在得知分组受损后,发送方如何处理——出错重传
【当发送方收到了接收方发送的NAKS时,选择重发】
下面来看一下 rdt 2.0 的有限状态机描述图,现在该数据传输协议(自动重传请求协议)采用了差错检测、肯定确认与否定确认。
rdt 2.0 的发送端有两个状态。在最左边的初始状态中,发送端协议正等待来自较高层传下来的数据。当触发 rdt_send(data)
事件时:
- 通过
sndpkt = make_pkt(data, checksum)
产生一个包含待发送数据且带有校验和的分组 - 然后将该分组通过
udt_send(sndpkt)
发送到信道中
执行完上述的两个动作后,发送端的状态变迁为“等待接收接收端的 ACK 或 NAK 分组”。接下来根据接收端的响应不同会有不同的变迁方案:
- 如果收到了一个 ACK 分组(
rdt_rcv(rcvpkt) && isACK(rcvpkt)
),那么发送端知道接收端已经成功接收到了刚才发送出去的分组,发送端状态回到初始状态,继续等待下一次由较高层传下来的数据发送请求 - 如果收到了一个 NAK 分组(
rdt_rcv(rcvpkt) && isNAK(rcvpkt)
),那么发送端知道接收端接收到的分组是受损的,所以调用udt_send(sndpkt)
重新发送该分组,然后状态不变,继续等待接收接收端的 ACK 或 NAK 分组
由于 rdt 2.0 的发送端拥有这个特性,所以 rdt 2.0 这样的协议被称为停等(stop-and-wait)协议。
rdt 2.0 的接收端仍然只有一个状态。状态变迁取决于收到的分组是否受损,有两种方式:
- 如果收到的分组受损,即
rdt_rcv(rcvpkt) && corrupt(rcvpkt)
,则返回 NAK 分组 - 如果收到的分组完好,即
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt)
,则返回 ACK 分组
处理完后仍然返回自身这个状态,继续等待下一次从底层接收分组并处理。
接收方发送ACK。发送方接收到ACK后无异样操作。
接收方收到的报文是有错误的,于是其发送NAK给发送方。发送方收到NAK后选择重传。
现在我们得到了一个似乎是可以在有比特差错信道上正常工作的可靠数据传输协议了,但仔细想想,我们没有考虑 ACK 或 NAK 分组受损的情况。如果 ACK 或 NAK 分组受损的时候,我们应该怎么做?
经具有比特差错信道的可靠数据传输协议 rdt 2.1 (解决 ACK 或 NAK 分组受损问题和重传问题)
1、解决ACK 或 NAK 分组受损的问题比较简单的一个方法ACK和NAK加校验和
Q:我们怎么知道重传的分组是我们需要的分组呢?
2、解决只考虑重传可能会出现大量重复分组问题的方式是:是在数据分组中添加一个新的字段,然后让发送端对其数据分组编号,将发送数据分组的序号放在该字段中。于是,接收端只需要检查序号就可以确定收到的分组是否是一次重新传送的分组。因为 rdt 2.0 是一个简单的停等协议【发送方发出一个分组,然后等待接收方的应答】,1 比特序号就足够了。
在这里再次提醒一下我们在 rdt 2.0 开始的地方所做的假设:假设信道不丢分组,而且不会存在分组乱序的情况。所以发送端知道所接收到的 ACK 和 NAK 分组(无论是否受损)都是为响应其最近发送的数据分组而生成的。
完善了对 ACK 和 NAK 分组受损的情况的处理机制后,我们把完善后的协议称为 rdt 2.1,下面是 rdt 2.1 发送端的有限状态机描述图:
现在的状态数是以前的两倍,是因为协议的状态必须反映出目前(由发送端)正发送的分组或(在接收端)希望接受的分组序号是 0 还是 1。看起来这个描述图很复杂,其实发送或期望接收 0 号分组的状态中的动作与发送或期望接收 1 号分组的状态中的动作是相似的,唯一不同的是序号处理的方法不同。
这里我按照上图来描述一下 rdt 2.1 协议发送端的状态变迁过程:
首先由较高层触发
rdt_send(data)
事件,通过sndpkt = make_pkt(0, data, checksum)
产生一个序号为 0,包含待发送数据且带有校验和的分组,接着通过udt_send(sndpkt)
将其发送到信道中,然后状态变迁为“等待接收接收端的 ACK 或 NAK 0”当发送端收到了一个来自接收端的分组数据:
- 如果该分组数据受损,或者接收到的是 NAK 分组,那么通过
udt_send(sndpkt)
重新传送刚才的序号为 0 的分组到信道中 - 如果该分组完好且收到的是 ACK 分组,那么发送端知道接收端已经成功接收了刚才发送的序号为 0 的分组,此时发送端状态变迁到等待较高层传下来的数据发送请求
- 如果该分组数据受损,或者接收到的是 NAK 分组,那么通过
接着再次由较高层触发
rdt_send(data)
事件,通过sndpkt = make_pkt(1, data, checksum)
产生一个序号为 1,包含待发送数据且带有校验和的分组,接着通过udt_send(sndpkt)
将其发送到信道中,然后状态变迁为“等待接收接收端的 ACK 或 NAK 1”当发送端再次收到了一个来自接收端的分组数据:
- 如果该分组数据受损,或者接收到的是 NAK 分组,那么通过
udt_send(sndpkt)
重新传送刚才的序号为 1 的分组到信道中 - 如果该分组完好且收到的是 ACK 分组,那么发送端知道接收端已经成功接收了刚才发送的序号为 1 的分组,此时发送端状态变迁到等待较高层传下来的数据发送请求(即回到本状态机的初始状态)
- 如果该分组数据受损,或者接收到的是 NAK 分组,那么通过
只要收到NAK就重发,收到ACK就发下一个。
接着再来描述一下 rdt 2.1 协议接收端的状态变迁过程:
- 首先在初始状态上,接收端等待着接收由发送端发来的序号为 0 的分组数据
- 接着由
rdt_rcv(rcvpkt)
从底层信道接收了一个分组数据:- 如果该分组受损(即
rdt_crv(rcvpkt) && corrupt(rcvpkt)
),那么由sndpkt = make_pkt(NAK, checksum)
产生一个附带校验和的 NAK 分组,接着由udt_send(sndpkt)
发送回发送端 - 如果该分组失序(即
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq1(rcvpkt)
),那么由sndpkt = make_pkt(ACK, checksum)
产生一个附带校验和的 ACK 分组,接着由udt_send(sndpkt)
发送回发送端 - 如果该分组完好且顺序正确(即
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq0(rcvpkt)
),那么通过extract(rcvpkt, data)
和deliver_data(data)
将分组数据上传给较高层程序。接着,由sndpkt = make_pkt(ACK, checksum)
产生一个附带校验和的 ACK 分组,由udt_send(sndpkt)
发送回发送端
- 如果该分组受损(即
- 接下来等待序号为 1 的分组的处理过程与上面类似,不再赘述
只要分组有错就发NAK,只要分组没错(失序或没有失序)就发ACK。
总结一下:
发送方在发送的分组是0或者1时都需要确认是否发送的分组被正确的接受。如果没有就重发。所以有2、4两个相同的状态。
接受端收到失序分组的原因:
这里顺便解释一下接收端接收到失序分组的原因:假设发送端发送序号为 0 的分组,接收端收到并回复 ACK,接着接收端就开始等待接收序号为 1 的分组,但是这个接收端返回的 ACK 分组由于在传输过程中受损,发送端并不知道序号为 0 的分组已经发送成功,所以仍然重复发送序号为 0 的分组,这样,就造成了接收端在等待接收序号为 1 的分组的时候,却接收到了序号为 0 的失序分组。
为什么当接收端接收到分组失序时要返回 ACK 分组呢?因为按照上面的假设,信道不会丢失分组,也不会乱序,所以收到失序的分组的唯一原因就是上面解释的这种,那么在这种情况下,只需要告诉发送端:我确实已经收到了你刚才一直重发的分组,可以发新的了。所以接收端回应 ACK 分组即可。
经具有比特差错信道的可靠数据传输协议 rdt 2.2 (无 NAK 分组)
其实上面的 rdt 2.1 协议在上述假设的底层信道模型中已经工作的不错了,但是我们还可以再简化一下,实现一个无 NAK 的可靠数据传输协议,我们称它为 rdt 2.2。
只使用ACK
取消NAK,接收方对最后一个正确收到的分组发送 ACK
【接收方必须明确指出被确认的分组的序号】
发送方收到的重复的ACK将按照NAK来进行处理
【重传正确的分组】
rdt 2.1 和 rdt 2.2 之间的细微变化在于,接收端此时必须包括由一个 ACK 报文所确认的分组序号(可以通过在接收端有限状态机中,在 make_pkt()
中包括参数 ACK 0 或 ACK 1 来实现),发送端此时必须检查接收到的 ACK 报文中被确认的分组序号(可通过在发送端有限状态机中,在 isACK()
中包括参数 0 或 1 来实现)。
下图是 rdt 2.2 协议发送端的有限状态机描述图:
下图是接收端的有限状态机描述图:
考虑在 rdt 2.1 协议中,如果接收端收到了一个受损的分组则会返回 NAK 分组。但是**==如果不发送 NAK,而是对上次正确接收的分组发送一个 ACK,也能实现与发送 NAK 一样的效果。==**(失序分组的话,就告诉发送方发新的,是新的分组错误的话,发之前收到的ACK也告诉发送方发新的。) 发送端接收到对同一个分组的两个 ACK(即接收冗余ACK)后,就知道接收端没有正确接收到跟在被确认两次的分组后面的分组。这就是 rdt 2.2 可以取消 NAK 分组的原因。
具体 rdt 2.2 的流程因为和 rdt 2.1 基本类似,故不赘述。
经具有比特差错的丢包信道的可靠数据传输协议 rdt 3.0(解决数据或者ACK会丢失问题)
新的假设:底层信道会丢包 (数据或 ACK)
Q:怎么检测丢包和丢包后怎么做?
A:发送方检测丢包和恢复丢包。
解决方法:发送方对ACK等待“适当的”时间
如果在这个时间内没有收到ACK则重传
如果分组或ACK仅仅是延迟到达(而非丢失):
重传将造成重复,但序号可以解决这个问题
接收方必须指出确认的分组序号
解释:发送端负责检测和回复丢包工作。假定发送端传输一个数据分组,该分组或者接收端对该分组的 ACK 发生了丢失。在这两种情况下,发送端都收不到应当到来的接收端的响应。所以,如果发送端愿意等待足够长的时间以确定该分组缺失已丢失,则它只需要重传该数据分组即可。
但是等待多长时间合适呢?很明显发送端至少需要等待:发送端与接收端之间的往返时延(可能会包括在中间路由器的缓冲时延)加上接收端处理一个分组所需的时间。但这个时间是很难估算的。在 RFC 1323 中,这个时间被假定为 3 分钟。
在实践中,发送端明智地选择一个时间值,以判定可能发生了丢包(尽管不能确定)。如果在这个时间内没有收到 ACK,则重传该分组。注意到如果一个分组经历了特别大的时延,发送端可能会重传该分组,即使该数据分组及其 ACK 都没有丢失。这就在发送端到接收端的信道中引入了冗余数据分组的可能性。不过上面的 rdt 2.2 协议已经有足够的功能(即序号)来处理冗余分组情况。
从发送端的观点来看,重传是万灵药。发送端不知道是一个数据分组丢失,还是一个 ACK 丢失,或者只是该分组或 ACK 过低延时。在所有这些情况下,发送端执行的动作都是重传。
为了实现基于时间的重传机制,需要一个倒计时计时器,在一个给定的时间量过期后,中断发送端。因此发送端需要能做到:
- 每次发送一个分组(包括第一次分组和重传分组)时,便启动一个定时器
- 响应定时器中断(采取适当的动作)
- 终止定时器
rdt3.0举例:
rdt3.0收到错误序号的ack,并不是立马重传,而是超时重传。
rdt3.0性能分析:
rdt3.0 可以工作, 但是性能很差
解释:正常情况下L/R(分组离开发送端)的时间后发送方就要发送新的数据。但是由于rdt3.0需要接受ACK。其中发送方接受ACK的时间为RTT+L/R(忽略ACK分组的传输时间,RTT是传播时间)。这段时间内发送方是在等待的。
结果:
发送方只有万分之2.7 的时间是忙的
每30ms(1个RTT)内只能发送1KB : 1 Gbps 的链路只有33kB/sec(1KB/30ms) 的吞吐量
网络协议限制了物理资源的利用率
图示:
提高性能的一种可行方法:流水线技术
流水线可靠数据传输协议(提高效率)
允许发送方发送多个分组而无需等待确认
- 必须增大序号范围(不再局限于3.0中的一个ACK了。正是3.0如此,故采取流水线作业)ACK(1-XXX)
- 协议的发送方和接收方必须对分组进行缓存
例如:
现在有一个问题:当流水线技术中丢失了一个分组,怎么进行重传?
- Go-Back-N(GBN,回退N步)协议:其后分组全部重传
- 选择重传(SR)协议:仅重传该分组
go-Back-N(回退N重传协议):
1.发送者在流水线中最多有 N 个未确认的数据报。
2.接收者仅发送累计的确认 ,如果中间有数据报缺失,就不予以确认。
3.发送者对最久未确认的数据报进行计时,如果计时器到点, 重传所有未确认的数据报。
4.发送窗口大于1 ≤ 2k-1,接受窗口等于1(也就意味着如果某一个报文段出现错误,那么接受窗口会停留再次,之后收到的数据将会被丢弃)
selective repeat(选择重传协议):
1.发送者在流水线中最多有 N 个未确认的数据报。
2.接收者对单个数据报进行确认。
3.发送者对每一个未确认的数据报进行计时,如果计时器到点, 仅重传该个未确认的数据报。
4.发送窗口大于1,接受窗口大于1(意味着可以缓存出错位置之后的报文段),最好是两者相同,
Go-Back-N:(累计确认)
允许发送方发送多个分组而不需等待确认,但已发送但未确认的分组数不能超过N。
限制滑动窗口大小:为了流量控制
ACK-only: 对正确按序到达的分组发送ACK
- 可能会产生重复的ACK
- 需要记住期待序号 expectedseqnum
失序分组或损坏分组:
- 丢弃 (不缓存) -> 接收方无缓存!
- 重发正确按序到达的最高序号分组的ACK(告诉发送方,该分组的后序分组(ack大于此ack)有问题,要重发)
- 每次发送的ACK一定是对正确按序到达的最高序号分组的确认
Go-Back-N协议特点
ACK(n): 接收方对序号n之前包括n在内的所有分组进行确认 - “累积 ACK”(一段ACK都确认后或者没收到后再进行重传)
对所有已发送但未确认的分组统一设置一个定时器
超时(n):重传分组n和窗口中所有序号大于n的分组
接收方收到失序分组:
丢弃 (不缓存) -> 接收方无缓存!
重发按序到达的最高序号分组的ACK
选择重传(超时重传)
解决GBN大量重传分组的问题
接收方逐个对所有正确收到(即使失序)的分组进行确认(不是累积确认)
对接收到的(失序)分组进行缓存(GBN不缓存), 以便最后对上层进行有序递交
发送方只重发怀疑丢失或损坏的分组
发送方为每一个没有收到ACK的分组设置定时器
发送窗口
大小为N,范围[sendbase, sendbase + N - 1]
限制已发送但未被确认的分组数最多为N
sendbase以前的分组都被确认
接受窗口窗口
大小为N,范围[recvbase, recvbase + N - 1]
落在窗口内的序号都是期待收到的分组序号
recvbase前都是按序到达,已发出确认,且已递交给上层
图示:
补充:为什么发送方会收到比sendbase还小的分组确认?
假设窗口位于sendbase-1时,序号sendbase-1的分组定时器时间还没收到ACK(sendbase-1),发送方重发该分组。这时又收到了ACK(sendbase-1),窗口前移。移动后,发送方又收到了ACK(sendbase-1)
前移的标志:确认收到rcvbase前的分组。
为什么接受方会收[recvbase-N,recvbase - 1]范围内的分组?并且必须给出确认?
- 因为确认可能会丢失。假设接受方按序收到N个分组,向发送方发送确认后接受窗口向前移动N位。
- 假设确认分组全部丢失,导致发送方重发。发送方最多只能发N个,因此接受方会收到[recvbase-N,recvbase - 1]范围内的分组
- 接收方这时必须给出确认,否则发送方窗口无法向前移动
为什么接受方收到比recvbase-N更早的分组后不用发确认了?
- 因为比recvbase-N更早的分组(如recvbase-N-1),发送方一定收到确认了。
- 当接受窗口位于recvbase时,意味着接受方一定按序收到了从[recvbase-N, recvbase- 1]的分组
- 这意味着发送方窗口一定到了recvbase-N。因此发送方一定收到了比recvbase-N更早的确认
总之,接受方窗口移动前一定要考虑发送方的窗口移动。要清楚一点:接受方是发出ACK的一方,它的窗口移动是在发送方的前面的。
当发送窗口和接受窗口不同步会产生严重后果。
第一种情况:
第二种情况:
这是接受方在窗口太大时的两难:最后收到的分组0是新的分组还是重传的?(接受方无法判断是情况一还是二)
结论:N≤2^k-1^(窗口长度必须小于等于序列号空间大小的一半)
第五部分:面向连接的传输 : TCP
TCP的特点:
基于字节流
面向连接
可靠传输
缓冲传输
全双工
流量控制
TCP报文段的首部格式
TCP虽然是面向字节流的,但TCP传送的数据单元却是报文段.
TCP报文段首部前20个字节是固定的, 后4n个字节是不定的
源端口和目的端口 : 各占两个字节.
**序号 : 占4个字节, 在一个TCP连接中传送的字节流中的每一个字节都按顺序编号. 整个要传输的字节流的起始序号必须在连接建立时设置. ** 序号字段的值则指的是本报文段所发送的数据的第一个字节在整个报文字节流中的序号
确认号 : 占4个字节, 期望收到对方下一个报文段的第一个数据字节的序号.
TCP是全双工的,主机A在向主机B传输数据的同时,也从主机B中接受数据。确认号仅当ACK标志为1时有效。确认号表示期望收到的下一个字节的序号
数据偏移 : 占4位, TCP报文段的数据起始处距离TCP报文段的起始处有多远.
保留 : 占6位,保留为今后使用,但目前应置为 0
紧急URG : URG = 1时, 紧急指针字段有效, 优先发送, 比如终止指令.
确认ACK : 当ACK = 1时, 才有效. TCP规定, 建立连接后所有传达的报文段ACK必须置为1.
推送PSH : 接收 TCP 收到推送比特置 1 的报文段,就尽快地交付给接收应用进程,而不再等到整个缓存都填满了后再向上交付。
复位RST :当 RST = 1 时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。
同步SYN : 在建立连接时同步序号. 当SYN = 1, 而ACK = 0时, 表明是一个连接请求报文. 如果对方同意建立连接, 则应在相应的报文段中使用SYN = 1和ACK = 1.
终止FIN : 当FIN = 1, 表示报文发送完毕, 可以释放连接.
窗口 : 占2字节, 发送报文一方的接收窗口. 窗口值得意义: .作为接收方让发送方设置其发送窗口的依据. 窗口字段明确指出了现在允许对方发送的数据量. 窗口值是动态变化的. 因为接收方的数据缓存空间是有限的.
校验和 : 占2字节, 检验范围包括首部和数据这两部分. 和UDP的检验和类似.
紧急指针 : 占2字节, URG = 1才有意义.
选项字段 : 长度可变, TCP 只规定了一种选项,即最大报文段长度 MSS (Maximum Segment Size)。MSS 告诉对方 TCP:“我的缓存所能接收的报文段的数据字段的最大长度是 MSS 个字节。”
填充字段 :这是为了使整个首部长度是 4 字节的整数倍。
序列号和确认号是整个报文段首部最重要的两个字段
TCP如何保证可靠性
UDP传输数据不可靠,具体表现:
- 发送方不知道UDP数据段传达到接收方了没有,无反馈信息
- 发送方有多少就发多少,不会理会接收方实际可接收的数据大小
因此可靠性传输需要考虑两个方面:
- 接收到消息后的反馈机制
- 接收方对于数据的承载能力
有以下几个点:
1)应用数据被分割成TCP认为最合适发送的数据块。称为段(Segment)传递给IP层
【流水线方式发送报文段】
2)当TCP发出一个段后,它会启动一个定时器,等待目的端确认收到这个报文段。若没有及时收到确认,将重新发送这个报文段(最早未确认的报文段)见“超时重传”
3)当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送的,通常将推迟几分之一秒。
【累积确认:只确认最后一个正确按序到达的报文段,见“反馈机制”】
4)TCP将保持它首部和数据的校验和,这是一个端到端的校验和,目的是检测数据在传输过程中的任何变化。如果收到段的校验和有差错,TCP将丢弃这个报文也不进行确认(超时重传导致对方就会重复发送了)。
5)TCP承载与IP数据报来传输,而IP数据报可能会失序,所以TCP的报文段到达时也可能会失序。但是TCP收到数据后会重新排序到正确的顺序(通过序号)。
6)IP数据报会发生重复,TCP的接收端必须丢弃重复是数据
7)TCP还能提供流量控制,TCP连接的每一方都有一定大小的缓冲空间
反馈机制
链路层也使用了可靠数据的传输,通过停等协议、滑动窗口协议等,但由于网络层的IP协议是不可靠的,即使在链路层上不会发生丢失和错序,但是在路由器(网络层)上可能发生丢包。
TCP传输使用选择性重传(selective repeat)的传输方式。不选用停等协议是因为传输层中端到端的延迟很大。选择性重传用到了序列号和确认号
确认号+序号
客户端发送第一个报文段,序号为x;第一个确认号字段为y-1。
然后服务端成功接收报文段后,提供一个确认,确认号为x+1,表示已经收到x以及之前的数据,期待接收x+1以及之后的数据。这里的报文段序号设置为y.
接着第三个报文段由客户发往服务器。确认已经收到服务器发来的数据。确认号为y+1,并且序号为x+1.
超时重传
在发送一个数据之后,就开启一个定时器,若是在这个时间内没有收到发送数据的ACK确认报文,则对该报文进行重传,在达到一定次数还没有成功时放弃并发送一个复位信号。
这里比较重要的是重传超时时间,怎样设置这个定时器的时间(RTO),从而保证对网络资源最小的浪费。因为若RTO太小,可能有些报文只是遇到拥堵或网络不好延迟较大而已,这样就会造成不必要的重传。太大的话,使发送端需要等待过长的时间才能发现数据丢失,影响网络传输效率。
由于不同的网络情况不一样,不可能设置一样的RTO,实际中RTO是根据网络中的RTT(传输往返时间)来自适应调整的。具体关系参考相关算法。
通过图来了解重传机制:
超时间隔加倍
每一次TCP超时重传均将下一次超时间隔设为先前值的两倍
超时间隔由EstimatedRTT和DevRTT决定,每当发生下列事件之一是重新计算超时间隔
收到上层应用的数据
收到对未确认数据的ACK
快速重传(缩短了超时的时间)
TCP采取的是累计确认机制(不是说TCP要收完全部窗口中的ACK再发下一窗口),即当接收端收到比期望序号大的报文段时,便会重复发送最近一次确认的报文段的确认信号,我们称之为冗余ACK(duplicate ACK)。这个和收到错误分组,接收方不发ACK是没有关联的。换句话说,累计确认机制让TCP具备了快速重传的特点。
【增加重发丢失分组的延时】
通过重复的ACK检测丢失报文段
发送方常要连续发送大量报文段
如果一个报文段丢失,会引起很多连续的重复ACK.
如果发送收到一个数据的3个重复ACK,它会认为确认数据之后的报文段丢失
快速重传: 在超时到来之前重传报文段
TCP重传机制和GBN/SR的关系
注意:这三者的发送窗口是收到之前的ACK就滑动到下一个分组
TCP重传机制更多的像一种GBN和SR的混合机制
TCP积累是确认式的,只用一个定时器,很像GBN
但有区别:
很多TCP实现缓存失序的报文段。
GBN在报文段n超时时,会重发从n开始所有未确认的报文段。而TCP只会重传报文段n。甚至如果在报文段n超时收到了对报文段n+1的确认,TCP连报文段n都不会重传。
TCP还有快速重传机制。
流量控制
流量控制的原因:
- TCP接收方有一个缓存,所有上交的数据全部缓存在里面
- 应用进程从缓冲区中读取数据可能很慢
所以TCP支持根据接收端能力来决定发送端的发送速度。这个机制叫做流量控制。
流量控制的目标:
发送方不会由于传得太多太快而使得接收方缓存溢出
流量控制的手段:
接收方在反馈时,将缓冲区剩余空间的大小填充在报文段首部的窗口字段中,通知发送方
窗口大小
(接收端向发送端主机通知自己可以接受数据的大小,这个大小限制就叫做窗口大小)
窗口扩大因子M
接收端如何把窗口大小告诉发送端呢? 回忆我们的TCP首部中, 有一个16位窗口字段, 就是存放了窗口大小信息;那么问题来了, 16位数字最大表示65535, 那么TCP窗口最大就是65535字节么?实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M,
实际窗口大小是 窗口字段的值左移 M 位;
机理
接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;窗口大小字段越大, 说明网络的吞吐量越高;
接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;发送端接受到这个窗口之后, 就会减慢自己的发送速度;
如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端
当接收端收到从3001号开始的数据段后其缓冲区挤满。不得不暂时停止发送数据,之后窗口收到更新通知后才得以继续进行。如果这个通知在途中丢失了,可能导致无法继续通信。所以发送方会是不是发送一个窗口探测的数据段。此数据端仅含一个字节来获取最新的窗口大小。
TCP连接的建立
就是我们常说的通过三次握手建立TCP连接。
第一步:客户端的TCP想服务器端的TCP发送一个特殊的TCP报文段,称为SYN报文段(syn设置为1),不包含应用层数据,随机选择一个初始序号client_isn。该报文段会被封装到一个IP数据报中,并发送给服务器。(告诉服务器,我来了)
第二步:服务器收到ip数据报后,提取出TCP SYN报文段,为该TCP分配TCP缓存和变量,并向该客户TCP发送允许连接的报文段。这个报文段也不包含应用层数据。首部包含三个重要信息:SYN比特设置为1,确认号设置为client_isn+1;服务器选择自己的随机序号server_isn。该报文也被称为SYNACK segment(服务器告诉客户端你可以连接,我准备好了)
第三步:客户机收到SYNACK报文段后,给该连接分配缓存和变量。客户机向服务器发送另外一个报文段,对服务器的允许连接的报文段进行确认(确认字段设置为server_isn+1)。因为连接已经建立了,因此SYN比特设置为0。这一阶段可以在报文段负载中携带客户到服务器的数据。(客户告诉服务器我不会鸽你的,我马上就要来了)
为什么要三次握手?
第三次握手是为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误。
假设只需要二次握手,考虑这种情况:
1:主机A发出的请求报文段在某些网络节点滞留时间太长,主机A由于超时重发连接请求,B收到重发的连接请求后给出同意连接的确认,主机A收到B的确认建立连接。数据传输完毕释放连接。
2:这时第一个请求才到达B,主机B收到该失效的请求后,误以为A又发出请求,于是向主机A发出确认,同意建立连接。主机A则不会理睬该确认。主机B则苦等A的数据。三次握手就可以防止这种情况的发生。(主机A不会对主机B的确认发出确认,连接就建立不起来)
SYN洪泛攻击
TCP服务器为了响应一个SYN连接请求报文,需要初始化连接变量及分配缓存(需要消耗服务器的资源),然后发送SYNACK报文进行响应,并等待客户端的ACK报文段。
如果客户机不给出第三次ACK响应来完成第三次握手的最后一步,服务器在等待一段时间后(通常为一分钟)服务器将终止该半开连接并释放资源。
第六部分:拥塞控制原理
在过多的源发送了过多的数据,超出了网络的处理能力。
不同于流量控制!
流量控制是为了匹配发送方和接收方的速度,只发生在发送方和接收方之间
拥塞控制:网络拥塞时,所有发送方都要抑制发送速度
拥塞的现象:
丢包 (路由器缓冲区溢出)
延时长 (在路由器缓冲区排队)
拥塞控制的方法(TCP选择端系统自己判断是否拥塞)
网络辅助的拥塞控制
直接网络反馈:路由器以阻塞分组的形式通知发送方“网络拥塞了”
经由接收方的网络反馈:路由器标识从发送方流向接收方分组中的某个字段以指示拥塞的产生,由接收方通知发送方“网络拥塞了”
端到端拥塞控制
网络层不为拥塞控制提供任何帮助和支持
端系统通过对网络行为(丢包或时延增加)的观测判断网络是否发生拥塞
目前TCP采用该种方法
TCP拥塞控制
由于发送方到接收方之间的信道是公用的,因此如果发送方不考虑中间信道的容量随意发送就可能出现拥塞。拥塞会导致延迟严重,甚至大量丢包
TCP必须使用端到端拥塞控制。(区别于网络辅助手段)
方法:让每一个发送方根据所感知到的网络拥塞程度来限制其能向链接发送流量的速率。
不拥塞——加快发送流量的速率
拥塞——减慢发送流量的速率
发送端由一个接收缓存、一个发送缓存和几个变量组成。
拥塞控制机制需要跟踪一个额外的变量,拥塞窗口,cwnd。它对于一个TCP发送方能向网络中发送流量的速率进行了限制。
$LastByteSent−LastByteAcked<=min(cwnd,rwnd)$
这里我们假设接收窗口无限大,发送方未被确认的数据量仅受限于cwnd
$ 发送速率 = cwnd/RTT (字节/秒)$
可以通过调节cwnd来调整发送方向连接发送数据的速率
① 限制发送流量
运行在发送方的TCP拥塞控制机制跟踪一个额外的变量,即拥塞窗口(congestion window。cwnd)
调节cwnd值以控制发送速率
② 发送方感知出现拥塞
过度拥塞的时候,路由器的缓存会溢出,引起一个数据报被丢弃,引起发送方的丢包事件
丢包事件:出现超时或者收到来自接收方的三个冗余ACK
③ 发送方在感知拥塞后确定应当发送的速率
这里需要用到TCP拥塞控制算法:慢启动;拥塞避免;快速恢复
阶段 | 阶段行为 | 结束的方式 |
---|---|---|
慢启动 | cwnd的值以一个MSS开始并且每当传输的报文段首次被确认,就指数增加MSS,1,2,4…每经过一个RTT,发送的速率翻番。 | 第一次结束:(出现由超时指示的丢包时间)将cwnd设置为1,然后重新开始慢启动过程,并且设置ssthresh(慢启动阈值)设置为cwnd的一半。 第二次结束:(当cwnd达到或者超过ssthresh),TCP转移到拥塞避免阶段。 第三次结束(检测到3个冗余ACK):执行快速重传并进入快速恢复状态 |
拥塞避免 | 此时cwnd的值大约是上次拥塞的值的一半,每次收到确认,将cwnd增加一个MSS | 出现超时时,cwnd的值被设置为1个MSS,当丢包发生时,ssthresh更新为cwnd值的一半;当收到3个冗余的ACK时,将ssthresh的值记录为cwnd的值的一半,然后进入快速恢复状态 |
快速恢复 | 每收到一个冗余的ACK,cwnd将增加一个MSS | 当对丢失报文段的一个ACK到达时,TCP在降低cwnd后进入拥塞避免状态。如果出现超时事件,cwnd设置为1个MSS,并且ssthresh的值设置为cwnd值的一半,然后迁移到慢启动状态 |
MSS,最大报文段长度
此处引入一个概念程为拥塞窗口,拥塞窗口发送开始的时候, 定义拥塞窗口大小为1;每次收到一个ACK应答, 拥塞窗口加1;
每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;像上面这样的拥塞窗口增长速度, 是指数级别的. “慢启动” 只是指初使时慢, 但是增长速度非常快.为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.此处引入一个叫做慢启动的阈值。
当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长
**当TCP开始启动的时候, 慢启动阈值等于窗口最大值;在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1;**少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;
补充:
1、初始速率很低但是速率的增长速度很快。
2、对收到3个重复ACK的反应——快速重传
- 门限值设为当前CongWin的一半(门限值初始值65kB)
- 将CongWin减为新的门限值+3MSS
- 线性增大拥塞窗口
3、对超时事件的反应:设置门限(Threshold)
- 门限值设为当前CongWin的一半(门限值初始值65kB)
- 将CongWin设为1个 MSS大小;
- 窗口以指数速度增大
- 窗口增大到门限值之后,再以线性速度增大
在执行慢开始算法时,拥塞窗口 cwnd 的初始值为 1,发送第一个报文段 M0。
发送端收到 ACK1 (确认 M0,期望收到 M1)后,将 cwnd 从 1 增大到 2,于是发送端可以接着发送 M1 和 M2 两个报文段。
接收端发回 ACK2 和 ACK3。发送端每收到一个对新报文段的确认 ACK,就把发送端的拥塞窗口+1MSS。现在发送端的 cwnd 从 2 增大到 4,并可发送 M3 ~ M6共 4个报文段。
发送端每收到一个对新报文段的确认 ACK,就把发送端的拥塞窗口+1MSS,因此拥塞窗口 cwnd 随着传输次数按指数规律增长。
当拥塞窗口 cwnd 增长到慢开始门限值 ssthresh 时(即当 cwnd = 16 时),就改为执行拥塞避免算法,拥塞窗口按线性规律增长。
假定拥塞窗口的数值增长到 24 时,网络出现超时(表明网络拥塞了)。
更新后的 ssthresh 值变为 12(即发送窗口数值 24 的一半),拥塞窗口再重新设置为 1,并执行慢开始算法。
当 cwnd = 12 时改为执行拥塞避免算法,拥塞窗口按按线性规律增长,每经过一个往返时延就增加一个 MSS 的大小。
假定拥塞窗口的数值增长到 24 时,网络出现冗余ACK
TCP拥塞控制算法(Reno)总结
- 当 拥塞窗口CongWin小于门限值Threshold时,发送方处于 慢启动 阶段,窗口以指数速度增大。
- 当 拥塞窗口CongWin大于门限值Threshold时,发送方处于 拥塞避免 阶段,窗口线性增大。
- 当收到 3个重复的ACK 时,门限值Threshold设为拥塞窗口的1/2,而拥塞窗口CongWin设为门限值Threshold+3个MSS。
- 当 超时 事件发生时,门限值Threshold设为拥塞窗口的1/2,而拥塞窗口CongWin设为1个 MSS。
TCP吞吐量
忽略慢启动。
假定当丢包事件发生时,窗口大小为 W.
此时 吞吐量为$W/RTT$
丢包事件发生后,窗口大小减为W/2, 吞吐量为W/2RTT.
因此平均吞吐量为: $0.75 W/RTT$
TCP拥塞控制的公平性
由于TCP是具有拥塞机制的,所以TCP在带宽的处理上是公平的,但是UDP是没有拥塞控制的,所以UDP是不公平的。它会以恒定的速率传输数据。
在并行TCP连接中,由于TCP是公平的。当一个应用使用了多个并行的TCP连接,它就会占有更多的带宽。从应用的角度来开就不是公平的。