运输层位于应用层与网络层之间,该层为运行在不同主机上的应用进程提供直接的通信服务起着重要作用。
概述与运输层服务#
运输层协议为运行在不同主机上的应用进程之间提供了逻辑通信
逻辑通信大体是指在不考虑中间的链路连接的情况下,运行不同进程的主机好像是直接通过一条管道连接起来
和应用层协议相似,运输层协议仍是运行在端系统
中,并将应用层传输的报文转换为运输层报文段
那么从粗略的角度看,运输层报文是将报文划分为较小的块,并在其头部加上运输层首部组合而成的
1.1 与网络层的关系#
- 网络层提供了
主机
之间的逻辑通信 - 运输层为运行在不同主机上的
进程
间提供了逻辑通信
这里示例书上的一个例子:
翻译过来就是
应用层报文 = 信封上的字符
进程 = 堂兄弟姐妹
主机 = 家庭
运输层协议 = 分发的邮递员
网络层协议 = 邮政服务
同时我们需要注意的是,上层的服务往往受下层的服务所制约,而上层的协议能够提供特殊的服务(如TLS)
1.2 因特网运输层概述#
这里先提及下ip,ip是网络世界中主机的标识符,其位于网络层的服务之上
对于TCP和UDP服务来说,其最基本的责任就是将端系统间ip的交付服务
扩展为端系统上两个进程之间的交付服务
扩展的过程称作是运输层的
多路复用与多路分解
同时对于TCP和UDP来说,TCP为应用程序提供了几种附加服务
- 可靠数据传输(数据不丢失)
- 拥塞控制(控制传输时的平衡)
而UDP却是不可调节的,是一种不可靠的数据传输服务,能以任意速率发送数据
1.3 多路复用与多路分解#
对于一个进程来说,其有一个或多个套接字
(每个套接字均有其专属的标识符),在数据传输层的过程中,运输层报文段并没有直接交付给进程,而是将数据交给了其中的套接字,由套接字向网络层分发
这时我们引出多路复用与多路分解的概念:
- 将运输层报文段中的数据交付给正确的套接字的工作称为多路分解
- 从不同的套接字里接收数据块,并生成报文段,再传递到网络层,这个工作成为多路复用
总结一下,多路复用和多路分解有以下的要求
- 套接字有唯一标识符
- 报文段有特殊字段来指示所要交付到的套接字
这是报文段的大体结构
端口号是长度为
16bit
的数,其中0-1023为周知端口号,用于内部特殊的服务,剩下的则可自由进行分配
在讨论完多路分解与多路复用的基本内容后,接下来我们来谈一谈不同协议的多路复用与多路分解
无连接运输:UDP#
无连接传输:指在发送报文段前,发送方和接收方的运输层实体之间没有进行握手
UDP 是 User Datagram Protocol 的简称, 中文名用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,UDP 在 IP 报文的协议号是 17
这是基于UDP的报文段结构,可以看出作为一种最简洁的运输层协议,其除了复用/分解功能及少量的差错检测外,基本就没别的什么修饰了
UDP连接有如下特点:
- 关于发送什么数据以及如何发送的应用层控制更为精细
- 速度更快,延迟更低
- 在一定条件下,丢失的数据在能容忍的范围内
- UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界
- 吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制
- 无需建立连接
- 不需要握手即可建立连接,即’发送即结束’
- 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了
- 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作
- 无连接状态
- 不需要维护连接时的诸如缓存之类的状态
- 支持多播,组播等
- 分组首部开销小
- UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的
可靠数据传输原理#
在讨论该如何实现可靠数据传输之前,先让我们明确下可靠数据传输的定义:
- 即数据可以通过一条可靠的信道进行传输,在这一条信道之中,传输数据的比特不会受到损坏或丢失,而会按照发送顺序进行交付
这种形式上的传输很像无线传输时划分的信道,能使不同设备的通信得以划分,不会影响互相的通信
这种所被提供的服务我们称为可靠数据传输服务
可以看出这种可信赖的传输通道是建立在传输层之上
Q:为什么可靠传输是建立在传输层上的
A:作为逻辑上的传输,传输层的运输是不考虑真实的损失的,而在实际的传输中,建立在网络层上的ip服务在运输中是容易发生丢包现象的,所以要尽量保证逻辑上的交互成功
构建可靠数据传输协议#
在了解了可靠数据传输的基本信息之后,接下来让我们去了解如何去构建出可靠的数据传输
1. 经完全可靠信道的可靠数据传输: rdt_1.0#
每个部分称为有限状态机(Finite-State-Machine),简称
FSM
在这一类别中,通过rdt_send()产生一个分组,通过rdt_recv()接收一个分组,这样就算拥有了一个完全可靠的信道
2. 经具有比特差错信道的可靠数据传输: rdt_2.0#
在由rdt1.0建立了可靠的运输层传输模型后,接下来我们来看下分组中比特更容易受损的底层信道
现在先假定所有的分组是按顺序所发送的
对于接收一条信息,我们可能有肯定确认
或者否定确认
两种状态,前者即是OK状态,而后者却是受到了损失,需要重新确认
而这种因错误而需重传的协议在计算机中称为是自动重传协议
(ARQ)
ARQ协议规定了需要另外三种协议功能来处理存在比特差错的情况:
- 差错检测
- 即数据包具有额外的比特来进行差错校验,汇集在协议规定的分组检验和字段中
- 接收方反馈
- 对于肯定确认(ACK)和否定确认(NAK)两种状态,我们需要用
1bit
的0或1
来进行反馈
- 对于肯定确认(ACK)和否定确认(NAK)两种状态,我们需要用
- 重传
对于发送端:
- 先等待上层的调用,待rdt_send()发生后,经下层udt_send()发送分组
- 等待反馈(网络IO阻塞状态)
- 根据反馈进行下一次发送的准备
- ACK则准备下一个分组的发送
- NAK则进行重传
- 完成循环
对于接收端:
- rdt_rev()接收数据
- 根据校验结果返回对应的状态位
以上就是在理想条件下rdt2.0所发挥的作用了,那么如果状态位发生损坏了呢?
3. rdt2.0的修订版: rdt2.1#
对于这种情况,这里引入了序号
(SEQ)的概念,即我们只需要检查包的序号即可判断
其是否为重传
对于rdt2.0所规定的等停协议
来说,两个序列号(0, 1)已经足够使用
这里解释下具体的过程
一. 发送方获得来自上层的数据后,封包(编号为0)发送至接收方,接收方接收到分组后:
- 如果没有损坏,回送ACK分组,并等待编号为1的分组
- 如果损坏,回送NAK分组,并继续等待编号为0的分组
二. 发送方接收到回送分组后:
- 如果没有损坏,且为ACK分组,无任何动作,等待上层协议
- 如果没有损坏,且为NAK分组,重发编号为0的分组
- 如果损坏,无论是ACK还是NAK,重发编号为0的分组
三. 接收方收到分组后(接收方不知道自己的ACK或NAK分组在传输过程中是否损坏):
- 如果没有损坏,且为编号1的分组,回送ACK分组,且等待编号为0分组(循环)
- 如果没有损坏,且为编号0的分组,回送ACK分组,且等待编号为1分组
- 如果损坏,回送NAK分组,等待编号为0分组
4. rdt2.1的修订版: rdt2.2#
作为继承了rdt2.1大部分特性的版本,这里我们直接来讨论其所更新的内容
rdt2.2与2.1的区别就在于去掉了NAK,而在ACK分组中显式指出分组编号
一. 发送方获得来自上层的数据后,封包(编号为0)发送至接收方,接收方接收到分组后:
- 如果没有损坏,回送ACK0分组,并等待编号为1的分组
- 如果损坏,回送ACK1分组,并继续等待编号为0的分组
二. 发送方接收到回送分组后:
- 如果没有损坏,且为ACK0分组,无任何动作,等待上层协议
- 如果没有损坏,且为ACK1分组,重发编号为0的分组
- 如果损坏,无论是ACK1还是ACK0,重发编号为0的分组
三. 接收方收到分组后(接收方不知道自己的ACK分组在传输过程中是否损坏):
- 如果没有损坏,且为编号1的分组,回送ACK1分组,且等待编号为0分组(循环)
- 如果没有损坏,且为编号0的分组,回送ACK0分组,且等待编号为1分组
- 如果损坏,回送ACK1分组,等待编号为0分组
5. 经具有比特差错的丢包信道的可靠数据传输: rdt3.0#
在解决了包受损的情况后,这时如果在传输过程中发生了丢包
该如何去解决呢?
这里引入了倒计时定时器
的概念: 规定时间内,若无反馈,则重传
第一种:过程中未丢包,但是数据比特出错或者不符合序号,和我们讨论过的 rdt2.2 差不多,那么此时就没有任何动作,毫无作为就好了,等到时间间隔一到,当做超时处理,重发数据。
第二种:是真正的丢包了,所以时间一到,重新发送。
第三种最理想:啥事没有,一切正常,跳到下一个状态,等待发送下一个包
流水下你可靠数据传输协议#
在理想状态下,rdt3.0已经解决了可靠数据传输的问题,但是由于加上了过多了检测信息,其IO阻塞型的等停协议
对其性能造成了不小的阻碍
这时我们引入了流水线
的概念来对分组进行传输(类似于异步传输,IO不阻塞类型),即允许发送多个分组而无需等待确认
在引入了流水线的概念后
- 需增加序号的范围来为每个分组确定一个序号
- 双方要建立完善的缓存机制来处理分组
- 对错误进行进一步处理,这里有两种方式:
回退N步
和选择重传
回退N步(GBN)#
这个协议又称为滑动窗口协议
,引入了窗口的概念
这里发送方数据有四种状态:
- 已被确认:指已发送又被接收端成功确认的数据
- 发送但未被确认:已发送但还未被接收端确认,未接收到接收端的ACK,仍属于窗口内的内容
- 可用但未发送:已经可以进行发送但是还未发送的信息
- 不可用:超出了接收范围的部分
接收方数据的状态:
- Received and ACK Not Send to Process: 已接受也已经回复ACK,但是未向应用层多路分解,仍在缓存中
- Received Not ACK: 已经接收并,但是还没有回复ACK,这些包可能输属于Delay ACK的范畴了
- Not Received: 有空位,还没有被接收的数据
对于窗口的大小,这时接收方和发送方协商
得出的结果。对于超出的部分可以留到下一层整体发送的时候再发送
GBN发送方必须响应三种类型的事件:
- 上层的调用
- 调用rdt_send()时需判断是否能进入当前窗口,不可则进入缓存
- 或者使用同步机制控制rdt_send的使用
- 收到一个ACK
- 累积确认的计数
- 超时事件
同时gbn接收方在遇到乱序的分组时,并不是加入缓存,而是直接丢弃,等待发送方超时重传
选择重传#
显然对于GBN来说其已经解决了大部分可靠传输时的问题,但是在最后一点讨论GBN的错误处理时,还是能看到如果错了一个,将会有大量的分组进行重传
选择重传协议
(SR)
- 仅仅直接重发那些丢失或者受损的分组
- 对于正确接收但是顺序错误的,进入缓存,直到正确的序号为止(不返回ACK)
面向连接的运输: TCP#
TCP连接是面向连接的,在之间了解rdt时候就已经规定了诸如ACK之类的标志,而TCP连接进行的握手
正是利用了这些标志
TCP连接同其他可靠连接一样是点对点
的,同时属于全双工服务
# 这种tcp连接建立的过程称为是三次握手
clientSocket.connect((serverName, serverPort))
tcp连接具有自己的缓存(相当于滑动窗口的存在),同时具有最大报文长度
(MSS)来对报文的长度进行限制
在链路层中有最大长度限制 — 最大传输单元(MTU),一般MTU的大小为1500,MTU = MSS + TCP首部长度 + IP首部长度
1. TCP报文段结构#
序号和确认号#
TCP把数据看作是无结构的,有序的
字节流,因此一个报文段的序号是该报文段首字节
的字节流编号
字节序号
TCP 连接中,为传送的字节流(数据)中的每一个字节按顺序编号。也就是说,在一次 TCP 连接建立的开始,到 TCP 连接的断开
,你要传输的所有数据的每一个字节
都要编号。这个序号称为字节序号初始序号 ISN
当新连接建立的时候,第一个字节
数据的序号称为 ISN(Initial Sequence Number),即初始序号。ISN 一开始并不一定就是 1。在 RFC (规定网络协议的文档)中规定,ISN 的分配是根据时间来的。当操作系统初始化的时候,有一个全局变量假设为 g_number 被初始化为 1(或 0),然后每隔 4us 加 1. 当 g_number 达到最大值的时候又绕回到 0.当新连接建立时,就把 g_number 的值赋值给 ISN报文段序号
如果一个 TCP 报文段的序号为 301,它携带了 100 字节的数据,就表示这 100 个字节的数据的字节序号范围是 [301, 400],该报文段携带的第一个字节序号是 301,最后一个字节序号是 400.
而对于确认号来说,A填进报文段的确认号是主机A期望从主机B收到的下一字节的序号
TCP 连接管理#
要建立TCP连接要经历以下三个步骤(三次握手
):
- 先向服务器端发送一个特殊的报文段,此报文段
SYN
位(握手信号)为1,因此又称为SYN报文段,同时用ISN将seq赋值 - 当服务器接收到此报文段后,为该TCP连接分配缓存,同时返回的数据报中SYN为1,seq为服务器端生成,确认号为seq+1
- 客户端在接收到后也要为连接分配缓存,seq与ack同步,同时确认号加1,准备进入数据接收状态
要结束连接要经历以下四个步骤(四次挥手
)
- 第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。即发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认
- 第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段
- 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认
- 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态