1. 概述与运输层服务

首先需要简单了解下网络层,网络层有一个IP协议,为主机之间提供了逻辑通信。IP的服务模型是尽力而为交付服务,因此是不可靠服务

运输层有两种不同的运输协议,一种是UDP,一种是TCP。

它们最基本的责任是:将两个端系统间的交付服务扩展为运行在端系统上的两个进程之间的交付服务

将主机间交付扩展到进程间交付被称为运输层的多路复用 ( transport -layer multiplexing) 与多路分解( demultiplexing)

UDP 和 TCP 还可以通过在其报文段首部中包括差错检查字段而提供完整性检查。 进程到进程的数据交付和差错检查是两种最低限度的运输层服务,也是 UDP 所能提供的仅有的两种服务

TCP为应用程序提供了几种附加服务,不仅提供了可靠数据传输,还提供了拥塞控制。而UDP的流量是不可调节的。

2. 多路复用和多路分解

2.1 套接字

一个进程(作为网络应用的一部分)有一个或多个套接字(socket) ,它相当于从网络向进程传递数据和从进程向网络传递数据的门户

在接收主机中的运输层实际上并没有直接将数据交付给进程,而是将数据交给了一个中间的套接字。由于在任一时刻,在接收主机上可能有不止一个套接字,所以每 个套接字都有唯一的标识符

image.png

2.2 多路分解

每个运输层报文段中具有几个字段。 在接收端,运输层检查这些字段,标识出接收套接字,进而将报文段定向到该套接字

将运输层报文段中的数据交付到正确的套接字的工作称为多路分解 ( demultiplexing)。

具体方法:在主机上的每个套接字能够分配一个端口号,当报文段到达主机时,运输层检查报文段中的目的端口号,并将其定向到相应的套接字。 然后报文段中的数据通过套接字进人其所连接的进程

2.3 多路复用

在源主机从不同套接字中收集数据块,并为每个数据块封装上首部信息(这将在以后用于分解)从而生成报文段,然后将报文段传递到网络层,所有这些工作称为多路复用(multiplexing)

多路复用的要求:

  1. 套接字有唯一标识符
  2. 每个报文段有特殊字段来指示该报文段所要交付到的套接字。这些特殊字段是源端口号字段 (source port oumber field) 和目的端口号字段 (destination port nurnber field)

2.4 无连接的多路复用与多路分解

UDP的多路复用和多路分解基本和上述的原理相同

需要注意的是:一个 UDP 套接字是由一个二元组(目的IP地址和目的端口号)来全面标识的

因此,如果两个 UDP 报文段有不同的源地址和/或源端口号,但具有相同的目的地址和目的端口号,那么这两个报文段将通过相同的目的套接字被定向到相同的目的进程。

眼端口号的用途是什么呢? 在 A 到 B 的报文段 中,源端口号用作”退回地址”的一部分,即当 B 需要回发一个报文段给 A 时, B 到 A 的报文段中的目的端口号便从 A 到 B 的报文段中的源端口号中取值。


2.5 面向连接的多路复用与多路分解

TCP的多路复用和多路分解相比UDP复杂很多

TCP 套接字和 UDP 套接字之间的一个细微差别是, TCP 套接字是由一个四元组(源 IP 地址,源端口号,目的 IP 地址,目的端口号)来标识的

当一个 TCP 报文段从网络到达一台主机时,该主机使用全部4个值来将报文段定向(分解)到相应的套接字。 特别与 UDP 不同的是,两个具有不同源 IP 地址或源端口号的到达 TCP 报文段将被定向到两个不同的套接字

服务器主机可以支持很多并行的TCP套接字,每个套接字与一个进程相联系,并由其四元组来标识每个套接字(不过连接套接字与进程之间并非总是有着一一对应的关系)

image.png

3. 无连接运输:UDP

运输层最低限度必须提供一种复用/分解服务,以便在网络层与正确的应用级进程之间传递数据。

UDP 只是做了运输协议能够做的最少工作。 除了复用/分解功 能及少量的差错检测外,它几乎没有对IP协议增加别的东西

虽然UDP协议是不可靠的,但有许多应用更适合用 UDP,原因主要以下儿点:

  • 关于何时、发送什么数据的应用层控制更为精细
    • UDP和TCP不同,TCP有拥塞控制机制,当线路拥堵时会自动降低发送速率,保证可靠的交付,这就导致了TCP不能满足一些实时应用,这些应用可以容忍数据丢失,但需要要求最低发送速度
    • UDP则是一旦收到上层数据,就打包传递给网络层
  • 无需连接建立
    • TCP 在开始数据传输之前要经过气次握手。 UDP 却不需要任何准备即可进行数据传输。 因此 UDP 不会引人建立连接的时延
    • 这可能是DNS运行在UDP上的主要原因
  • 无连接状态
    • TCP 需要在端系统中维护连接状态。 此连接状态包括接收和发送缓存、拥塞控制参数以及序号与确认号的参数。UDP 不维护连接状态,也不跟踪这些参数,也因此能支持更多活跃用户
  • 分组首部开销小
    • 每个 TCP 报文段都有 20 字节的首部开销,而 UDP 仅有 8 字节 的开销

UDP 中缺乏拥塞控制能够导致 UDP 发送方和接收方之间的高丢包率,并挤垮了 TCP 会话,这是一个潜在的严重问题,所以TCP被建立在了越来越多的应用上,即使是实况流媒体应用,因为很多防火墙拦截UDP的流量

其实,使用 UDP 的应用是可以实现可靠数据传输的。 这可通过在应用程序自身上建立可靠性机制来完成(例如,可通过增加确认与重传机制来实现,如采用我们将在下一节学习的一些机制) 。 但这并不是无足轻重的任务, 它会使应用开发人员长时间地忙于调试

3.1 UDP报文段结构

image.png
UDP报文段的头部是8个字节,64个比特,包含了4个部分。

  • 源端口号
  • 目标端口号
  • 长度:指示了在 UDP 报文段中的字节数(首部加数据) 。
  • 校验和:接收方使用检验和来检查在该报文段中是否出现了差错。

3.2 UDP校验和

UPD校验和提供了差错检测的功能,但它对差错恢复无能为力

发送方的 UDP 对报文段中的所有 16 比特字的和进行反码运算, 求和时遇到的任何溢出都被反卷(把溢出的值和低16位做加法)。 得到的结果被放在 UDP 报文段中的检验和字段

举例:假如我们有3个16比特的字
image.png
在接收方,全部的 4 个 16 比特宇(包括检验和)加在一起。 如果该分组中没有引入差错,则显然在接收方处该和将是 1111111111111111

这是一个在系统设计中被称颂的端到端原则( end - enu principle) 的例子 [ Saltzer 1984] ,该原则表述为因为某种功能(在此时为差错检测)必须基于端到端实现”与在较高级别提供这些功能的代价相比,在较低级别上设置的功能可能是冗余的或几乎没有价值的 。”
关于端到端原则:在底层添加功能的代价是非常庞大的,网络核心负责的作用应该只有一个,那就是数据传输,这样既简化了网络的设计,而且还提高的了性能。所以很多功能是可以推迟到更高层实现,底层实现可能冗余且导致低性能

4. 可靠数据传输原理

4.1 为上层提供的抽象服务

为上层实体提供的服务抽象是: 数据可以通过一条可靠的信道进行传输。 借助于可靠信道,传输数据比特就不会受到损坏 (由 0 变为 1 ,或者相反)或丢失,而且所有数据都是按照其发送顺序迸行交付

实现这种服务抽象是可靠数据传输协议 (reliable data transfer protocol) 的责任。

4.2 前置条件

考虑到底层信道模型越来越复杂,我们将不断地开发一个可靠数据传输协议的发送方和接收方,这里有些前置条件。

我们将考虑当底层信道能够损坏比特或丢失整个分组时,需要什么样的协议机制。

这里贯穿我们讨论始终的一个假设是分组将以它们发送的次序进行交付,某些分组可能会丢失; 这就是说,底层信道将不会对分组重排序
image.png

  • 通过调用rdt_send()就可以调用数据传输协议的发送方(rdt就是可靠数据传输协议),然后会调用udt_send()不可靠信道传输
  • 在接收端,当分组从信道的接收端到达时,将会调用rdt_rcv()来接受
  • rdt协议想向更高层交付数据时,将使用deliver_data()

4.3 经完全可靠信造的可靠数据传输: rdt 1.0

首先,考虑最简单的情况,也就是信道是完全可靠的

该协议的有限状态机如下:
image.png

  • 引起变迁的事件显示在表示变迁的横线上方,事件发生时所采取的动作显示在横线下方。
  • 如果对一个事件没有动作,或没有就事件发生而采取了一个动作,将在横线上方或下方使用符号 A,以分别明确地 表示缺少动作或事件
  • FSM 的初始状态用虚线表示

发送端:
只通过 rdt_send(data) 事件接受来自较高层的数据,产生一个包含该数据的分组(经由make_pkt(data)动作) ,并将 分组发送到信道中 。

接收端:
在接收端, rdt通过 rdt_rcv(packet)事件从底层信道接收一个分组,从分组中取出数据(经由extract(packet, data) 动作) ,并将数据上传给较高层(通过deliver_data(data)动作) 。

有了完全可靠的信道,接收端就不需要提供任何反馈信息给发送方,因为不必担心出现差错!(注意到我们也已经假定了接收方接收数据的速率能够与发送方发送数据的速率一样,所以接收方不必让请求方传递的慢一点)

4.4 经具有比特差错信道的可靠数据传输: rdt 2.0

4.4.1 问题分析

在实际情况中,底层信道更为实际的模型是分组中的比特可能受损,我们眼下还将继续假定所有发送的 分组(虽然有些比特可能受损)将按其发送的顺序被接收。

4.4.2 基本思想

如何在这种信道研究一种可靠的通信协议呢?其实可以参考人类日常生活的做法。当两个人交谈时,如果有一方没听清另一方说的话,可能就会说:没听清,请求重复。听清了可能会说:OK

这种口述报文协议使用了肯定确认 (positive acknowledgment) (“ OK” )否定确认 (negative acknowledgment) (“请重复一遍” ) 。

通过这种类型的协议,可以让发送方知道哪些分组被正确接收,而哪些分组发送了错误,需要重传。

4.4.3 ARQ协议

在计算机网络环境中,基于这样重传机制的可靠数据传输协议称为自动重传请求( Automatic Repeal request , ARQ) 协议。

基本上, ARQ 协议中还需要另外三种协议功能来处理存在比特差错的情况:

  • 差错检测。首先,需要一种机制以使接收方检测到何时出现了比特差错,例如UDP的校验和字段。总的来说,需要在头部加上额外的比特来检测是否出错
  • 接收方反馈。因为发送方和接受方通常不在一台机器运行,所以发送方想知道分组是否被正确接受就需要接收方发送明确的反馈信息。例如肯定确认的ACK,否定确认的NAK。我们的时 rdt2.0 协议将从接收方向发送方回送 ACKNAK分组。 理论上,这些分组只需要一个比特长; 如用0表示NAK,用 1 表示 ACK
  • 重传。接收方收到有差错的分组时,发送方将重传该分组文。

4.4.4 有限状态机

该协议采用了:

  • 差错检测
  • 肯定确认
  • 否定确认

image.png
发送端

  • 第一个状态:等待上层调用

当产生rdt_send (data)事件时,发送方将产生一个包含待发送数据的分组 (sndpkt) , 带有检验和然后经由udt_ end(sndpkt) 操作发送该分组。

  • 第二个状态:等待ACK或者NAK

发送方协议等待来自接收方的 ACKNAK分组,如果收到一个 ACK分组则发送方知道最近发送的分组已被正确接收,因此协议返回到等待来向上层 的数据的状态,如果收到一个NAK分组,该协议重传最后一个分组并等待接收方为响应 重传分组而回送的ACKNAK

注意到下列事实很重要: 当发送方处于等待 ACK 或 NAK 的状态时,它不能从上层获得更多的数据。 由于这种行为, rdt2.0 这样的协议被称为停止等待( slop-and-wait) 协议

接收端
rdt 2.0接收方的 FSM仍然只有一个状态。 当分组到达时,接收方要么回答一个 ACK,要么回答一个 NAK,这取决于收到的分组是否受损

4.5 经具有比特差错信道的可靠数据传输: rdt 2.1

4.5.1 问题分析

rdt 2.0 协议看起来似乎可以运行了,但遗憾的是,它存在一个致命的缺陷。 尤其是我们没有考虑到 ACK 或 NAK 分组受损的可能性!

考虑处理受损 **ACK****NAK**时的 3 种可能性:

  • 对于第一种可能性,考虑在口述报文情况下人可能的做法
    • 如果说话者不理解来自接收方回答的 “OK” 或”请重复一遍”,说话者将可能问”你说什么?”接收方则将复述其回 答。 但是如果说话者的”你说什么?”产生了差错,情况又会怎样呢?接收者不明 白那句混淆的话是口述内容的一部分还是一个要求重复上次回答的请求,很可能回一句”你说什么?” 。 于是,该回答可能含糊不清了。无法解决问题
  • 第二种可能性是增加足够的检验和比特。使发送方不仅可以检测差错,还可恢复差错。 对于会产生差错但不丢失分组的信道,这就可以直接解决问题
  • 第三种方法是,当发送方收到含糊不清的 ACK 或 NAK 分组时,只需重传当前数据分组即可。然而,这种方法在发送方到接收方的信道中引人了冗余分组 。 冗余分组的根本困难在于接收方不知道它上次所发送的 ACK或 NAK 是否被发送方正确地收到。因此它无法事先知道接收到的分组是新的还是一次重传! 无法解决问题!


4.5.2 基本思想

解决这个新问题的一个简单方法(几乎所有现有的数据传输协议中,包括 TCP,都采用了这种方法)是在数据分组中添加一新字段,让发送方对其数据分组编号,即将发送数据分组的序号 (sequence number) 放在该字段。

于是,接收方只需检查序号即可确认接收到的分组是否是一次重传的分组,完美解决了第三种方法遇到的问题!

4.5.3 具体实现

对于停等协议这种简单情况, 1 比特序号就足够了,因为每次传输一组数据,可以用0和1来标识是否是重传数据

目前我们假定信道不丢分组。 ACKNAK分组本身不需要指明它们要确认的分组序号。 发送方知道所接收到的 ACKNAK分组(无论是否是含糊不清的)是为响应其最近发送的数据分组而生成的

4.5.4 有限状态机

该协议用了:

  • 差错检测
  • 肯定确认
  • 否定确认
  • 编号

image.png
image.png

可以看到,发送和接收方的状态都翻倍了,因为需要表示0和1两种编号的状态。

4.6 经具有比特差错信道的可靠数据传输: rdt 2.2

4.6.1 基本思想

rdt 2.1中,使用了肯定确认否定确认。其实,是可以只使用肯定确认,不使用否定确认的,从而降低复杂度。

如果不发送 NAK,而是对上次正确接收的分组发送一个 ACK,我们也能实现与 NAK一样的效果。 发送方接收到对同一个分组的两个ACK(即接收冗余 ACK ( duplicate ACK) )后,就知道接收方没有正确接收到跟在被确认两次的分组后面的分组

所以,rdt 2.2 是在有比特差错信道上实现的一个无 **NAK**的可靠数据传输协议

4.6.2 有限状态机

该协议用了:

  • 差错检测
  • 肯定确认
  • 编号

image.png
image.png

4.7 经具有比特差错的丢包信道的可靠数据传输: rdt 3.0

因为分组序号在 0 和 1 之间交替,因此 rdt 3. 0 有时被称为比特交替协议( altemating- bit protocol)

4.7.1 问题分析

现在假定除了比特受损外,底层信道还会丢包,这在今天的计算机网络(包括因特网)中并不罕见。 协议现在必须处理另外两个关注的问题:

  • 如何检测丢包
  • 发生丢包后该干什么

rdt 2.2中,已经能够使用校验和,序号和ACK分组,重传来解决第二个问题。为了解决第一个问题,需要引入些新的机制。

4.7.2 基本思想

这里主要是让发送方来检测是否丢包

丢包有两种可能:

  • 数据分组丢失
  • ACK分组丢失

在这两种情况下,发送方都收不到应当到来的接收方的响应。 如果发送方愿意等待足够长的时间以便确定分组已丢失,则它只需重传该数据分组即可。

主要问题是:发送方需要等待多久才能确定己丢失了某些东西呢?

实践中采取的方法是发送方明智地选择一个时间值,以判定可能发生了丢包(尽管不能确保) 。 如果在这个时间内没有收到 ACK,则重传该分组。这就在发送方到接收方的信道中引人了冗余数据分组( duplicate data packeL) 的可能性。幸运的是,由 2.2 协议已经有足够的功能(即序号)来处理冗余分组情况。

4.7.3 具体实现

为了实现基于时间的重传机制,需要一个倒计数定时器,在 一个给定的时间量过期后,可中断发送方

因此,发送方需要做到:

  • 每发送一个分组,启动一个定时器
  • 响应定时器中断
  • 中止定时器

4.7.4 有限状态机

该协议用了:

  • 差错检测
  • 肯定确认
  • 编号
  • 计时器

image.png
至此,我们得到了一个可靠数据传输协议!

4.8 流水线可靠数据传输协议

rdt 3. 0 是一个功能正确的协议,但并非人人都对它的性能满意,特别是在今天的高速 网络中更是如此。 rdt 3. 0性能问题的核心在于它是一个停等协议。

image.png
至于停等协议的性能低,是很容易理解的,它的信道利用率很低。
image.pngimage.png
解决问题的方式也很简单:不使用停等方式运行,允许发送方发送多个分组而无需等待确认

因为许多从发送方向接收方输送的分组可以被看成是填充到一条流水线,故这种技术被称为流水线( pipelining) 。 流水线技术对可靠数据传输协议可带来如下影响:

  • 必须增加序号范围,因为每个输送中的分组(不计算重传的)必须有一个唯一的序号,而且也许有多个在输送中未确认的报文
  • 协议的发送方和接收方两端也许必须缓存多个分组。发送方最低限度应当能缓冲 那些已发送但没有确认的分组。接收方或许也需要缓存那些 已正确接收的分组。
  • 所需序号范围和对缓冲的要求取决于数据传输协议如何处理丢失、损坏及延时过大的分组。 解决流水线的差错恢复有两种基本方法是:回退N步 (Go-Back- N, GBN) 和选择重传 (Selective Repeat. SR) 。

4.8.1 回退N步

在回退N步 (GBN) 协议中,允许发送方发送多个分组(当有多个分组可用时)而不需等待确认,但它也受限于在流水线中未确认的分组数不能超过某个最大允许数 N

image.png

  • 基序号是最早未确认分组
  • 下一个序号是最小未使用序号
  • N是窗口长度,GBN协议也被称为滑动窗口协议

  • [0,base-1]段内的序号对应于已经发送并被确认的分组

  • [base,nextseqnum-1]段内的序号对应于已经发送但还未被确认的分组
  • [nextseqnum-1,base+N-1]段内的序号表示能用于那些即将要被发送的分组
  • 最后,大于等于base+N的是不能使用的分组,直到流水线中未确认的分组得到确认

为什么要设置一个N来限制这些被发送的,未被确认的分组的个数呢?

  • 一个原因是流量控制
  • 另一个原因是拥塞控制

关于分组的设置
在实践中,一个分组的序号承载在分组首部的一个固定长度的字段中。如果分组序号字段的比特数是 k , 则该序号范围是 [ 0 , 2k-1] 。 在一个有限的序号范围内,所有涉及序号的运算必须使用模2k运算(即序号空间可被看作是一个长度为2k的环,其中序号2k紧接着序号0)

具体来说,TCP 有一个 32 比特的序号字段,其中的 TCP 序号是按宇节流里的字节进行计数的,而不是按分组计数

GBN协议过程详解

  • 发送方
    • 发送方需要响应三种类型的事件
      • 上层的调用
        • 当上层进行调用时,首先需要确认窗口是否满,如果没满,则产生分组并发送,并更新响应的变量。如果没满,则将数据返回给上层,告诉上层,窗口满了,稍后再试。
      • 收到ACK
        • 对序号为 n 的分组的确认采取累积确认( cumulative acknowledgment) 的方式表明接收方已正确接收到序号为凡的以前且包括 n 在内的所有分组。
        • 例如,发送了分组100和200,当收到200时,则也认为100也收到了。(其实和接收方也有关,接收方是按序接受的,乱序的丢弃,所以收到200必定收到了100)
      • 超时
        • GBN协议的名称就来源于此,如果出现超时,发送方重传所有已发送但还未被确认过的分组
  • 接收方
    • 接收方需要响应两种事件
      • 正确接收到期待的分组n,并且按序。
        • 为分组n发送一个ack,并交付数据给上层
      • 其他情况(错误接收的情况)
        • 丢弃该分组,并为最近按序接收到的分组重新发送ack

FSN描述如下:
image.pngimage.png

总结
这种方法的优点是接收缓存简单, 接收方不需要缓存任何失序分组。因此,虽然发送方必须维护窗口的上下边界及 nextseqnum 在该窗口中的位置但是接收方需要维护的唯一信息就是下一个按序接收的分组的序号

运行实例:
image.png

4.8.2 选择重传

顾名思义,选择重传 (SR) 协议通过让发送方仅重传那些它怀疑在接收方出错(即丢失或受损)的分组而避免了不必要的重传

这种个别的、按需的重传要求接收方逐个地确认正确接收的分组。 再次用窗口长度 N来限制流水线中未完成、未被确认的分组数。 然而,与 GBN 不同的是,发送方已经收到了对窗口中某些分组的 ACK。下图是两方能看到的序号空间,是不对称的
image.png
发送方

  • 需要响应三种事件
    • 从上层收到数据
      • 检查下一个可以用于发送的序号,如果位于窗体内,则打包数据发送。否则,和GBN一样,要么缓存,要么返回上级
    • 收到ACK
      • 如果收到ACK,倘若该分组序号在窗口口内,则 SR 发送方将那个被确认的分组标记为已接收。 如果该分组的序号等于 send_base, 则窗口基序号向前移动到具有最小序号的未确认分组处。 如果窗口移动了并且有序号落在窗口内的未发送分组,则发送这些分组。
    • 超时
      • 每个分组必须拥有其向己的逻辑定时器,因为超时发生后只能发送一个分组

接收方

  • 需要响应三种类型的收到
    • 序号在[rcv_base, rcv_base+N-1]内的分组被正确接收
      • 如果该分组以前没收到过,则缓存该分组,并返回ACK
      • 如果该分组的序号是rcv_base,则该分组以及以前缓存的序号连续的 (起始于 rcv_base的) 分组交付给上层。然后,接收窗口接向前移动分组的编号向上交付这些分组。
    • 序号在[rcv_base_N,rcv_base-1]内的分组被正确收到。在此情况下,必须产生一个 ACK,即使该分组是接收方以前已确认过的分组
    • 其他情况。 忽略该分组

举例:
image.png

还有一件很重要的事情:对于 SR 协议 而言,窗口长度比须小于或等于序号空间大小的一半,否则,接收方无法判断收到的序号是新的还是重传


4.9 可靠传输协议总结

机制 用途和说明
检验和 用于检测在一个传输分组中的比特错误
定时器

| 用于超时/重传一个分组,可能因为该分组(或其ACK) 在信道中丢失了。 由于当一个分组延 时但未丢失(过早超时) ,或当一个分组已被接收方收到但从接收方到发送方的 ACK 丢失时,可能产生超时事件,所以接收方可能会收到一个分组的多个冗余副本 | | 序号 | 用于为从发送方流向接收方的数据分组按顺序编号。 所接收分组的序号间的空隙可使接收方检测出丢失的分缀。 具有相同序号的分组可使接收方检测出一个分组的冗余副本 | | 确认 | 接收方用于告诉发送方一个分组或一组分组已被正确地接收到了。 确认报文通常携带着被确认的分组或多个分组的序号。 确认可以是逐个的或累权的,这取决于协议 | | 否定确认 | 接收方用于告诉发送方某个分组未被正确地接收。 否定确认报文通常携带着未被正确接收的分组的序号 | | 窗口、流水线 | 发送方也许被限制仅发送那些序号落在一个指定范围内的分组。 通过允许一次发送多个分组但未被确认,发送方的利用率可在停等操作模式的基础上得到增加。 我们很快将会看到,窗口长度可根据接收方接收和缓存报文的能力、网络中的拥塞程度或两者情况来进行设置 |

我们曾假定分组在发送方与接收方之间的信道中不能被重新排序,然而, 当连接两端的”信道”是一个网络时,分组重新排序是可能会发生的。

实际应用中采用的方法是,确保一个序号不被重新使用,直到发送方”确信”任何先前发送的序号为x的分组都不再在网络中为止。通过假定一个分组在网络中的”存活”时间不会超过某个固定最大时间量来做到这一点。