《计算机网络 自顶向下方法》学习笔记 第三章 运输层

问题

  • 运输层的功能是什么?
  • TCP与UDP的优劣比较?它们的应用场景分别是什么样的?
  • 为什么要为分组编号?
  • 为什么要为分组加上检验和?
  • TCP的拥塞控制是如何实现的?
  • 为什么TCP要使用滑动窗口协议?

正文

运输层的两个关键功能:

  • 将网络层的在两个端系统之间的交付服务拓展到运行在两个不同端系统上的应用层进程之间的交付服务
  • 控制运输层实体的传输速率以避免网络中的拥塞

运输层为运行在不同主机上的进程提供了逻辑通信。

因特网网络层协议有一个名字叫IP,即网际协议。IP为主机之间提供了逻辑通信。IP的服务模型是 尽力而为交付服务 。这意味着IP尽它“最大的努力”在通信的主机之间交付报文段,但它并不做任何确保。特别是,它不确保报文段的交付,不保证报文段的按序交付,不保证报文段中数据的完整性。由于这些原因,IP被称为 不可靠服务

多路复用与多路分解

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

那么接收主机是怎样将一个到达的运输层报文段定向到适当的套接字的呢?

每个运输层报文段中具有几个字段(源端口、目的端口),在接收端,运输层检查这些字段,标识出接收套接字,进而将报文段定向到该套接字。将运输层报文段中的数据交付到正确的套接字的工作成为 多路分解

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

无连接运输: UDP

DNS是一个通常使用UDP的应用层协议的例子。

UDP相较TCP的优势:

  • 关于何时、发送什么数据的应用层控制更为精细。TCP有拥塞控制、重传等机制。
  • 无需连接建立。
  • 无连接状态。不需要维护连接的状态。
  • 分组首部开销小。每个TCP报文段都有20字节的首部开销,而UDP仅有8字节的开销。
应用 应用层协议 下面的运输协议
电子邮件 SMTP TCP
远程终端访问 Telnet TCP
Web HTTP TCP
文件传输 FTP TCP
远程文件服务器 NFS 通常UDP
流式多媒体 通常专用 UDP或TCP
因特网电话 通常专用 UDP或TCP
网络管理 SNMP 通常UDP
路由选择协议 RIP 通常UDP
名字转换 DNS 通常UDP

UDP 报文段结构

RFC 768 UDP

              0      7 8     15 16    23 24    31  
             +--------|--------|--------|--------+ 
             |     Source      |   Destination   | 
             |      Port       |      Port       | 
             +--------|--------|--------|--------+ 
             |                 |                 | 
             |     Length      |    Checksum     | 
             +--------|--------|--------|--------+ 
             |                                     
             |          data octets ...            
             +---------------- ...                 

                  User Datagram Header Format

可靠数据传输原理

可靠数据传输协议(reliable data transfer protocol, rdt)
晚点补上协议改进之中涉及到的状态机的图

构造可靠数据传输协议

接下来,我们将一步步地研究一系列协议,它们一个比一个更为复杂,最后得到一个无错、可靠的数据传输协议。

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

这一阶段,我们假设数据经完全可靠信道进行数据传输,因为 完全可靠 ,所以这个协议不需要定义任何内容,我们称其为 rdt 1.0 。

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

如果数据通过信道传输时,可能发生比特差错,那么我们可以在协议中定义哪些内容来完成这一阶段的可靠数据传输呢?

自动重传请求协议和停等协议

在通常情况下,报文接收者在听到、理解并记下每句话后可能会说“OK”。如果报文接收者听到一句含糊不清的话时,他可能要求你重复刚才那句话。这种口述报文协议使用了 肯定确认(OK)否定确认(请重复一遍) 。这些控制报文使得接收方可以让发送方知道哪些内容被正确接收,哪些内容接收有误并因此需要重复。在计算机网络环境中,基于这样重传机制的可靠数据传输协议称为 自动重传请求协议(Automate Repeat reQuest, ARQ)

实际上,ARQ协议中还需要提供三种协议功能来处理存在比特差错的情况:

  • 差错检测。一种使接收方能够检测到何时出现了比特差错的机制。
  • 接收方反馈。因为发送方和接收方通常在不同端系统上执行,可能相隔数千英里,发送方要了解接收方情况(这里即是分组是否被正确接收)的唯一途径就是让接收方提供明明确的反馈信息给发送方。我们的rdt 2.0协议将从接收方往发送方回送ACK与NCK分组。理论上,这些分组只需要一个比特长:如用0表示NAK,用1表示ACK。
  • 重传。接收方收到有差错的分组时,发送方需要重传该分组。

发送方将不会发送一块新数据,除非发送方确信接收方已正确接收当前分组。由于这种行为,rdt 2.0这样的协议被称为停等协议(stop-and-wait)。

分组编号与冗余ACK

如果ACK或NAK分组在传输过程中受损怎么办?

  • 发送新的询问分组表明接收方无法理解该分组。但如果这个新的询问分组也受损了呢?
  • 增加足够的检验和比特,使发送方不仅可以检测差错,还可恢复差错。对于会产生差错但不丢失分组的信道,这样足以解决问题。
  • 当发送方收到含糊不清的ACK或NAK分组时,只需重传当前数据分组即可,也就是在发送方和接收方的信道中引入了 冗余分组 。冗余分组的根本困难在于接收方不知道它上次所发送的ACK或NAK是否被发送方正确地收到。因此它也无法分辨接收到的分组是新的还是一次重传!

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

于是,接收方只需要检查序号即可确定收到的分组是否是一次重传。

对于停等协议这种简单情况,给序号字段分配一个比特位就足够了。(0或是1)

改进之后的rdt 2.1协议使用了从接收方到发送方的肯定确认和否定确认。当接收到 失序 的分组时,接收方对所接收的分组发送了一个肯定确认。如果收到受损的分组,则接收方将发送一个否定确认。


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

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

rdt 2.1和rdt 2.2之间的细微变化在于,接收方此时必须包括由一个ACK报文所确认的分组序号,发送方此时必须检查接收到的ACK报文中被确认的分组序号。

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

现在假定除了比特受损外,底层信道还会丢包,这在今天的计算机网络(包括因特网)中并不罕见。协议现在必须处理另外两个关注的问题:怎样检测丢包以及发生丢包之后该做些什么。在rdt 2.2中已经研发的技术,如使用检验和、序号、ACK分组和重传等,使我们能给出后一个问题的答案。为解决第一个关注的问题,还需增加一种新的协议机制。

有很多方法可以用于解决丢包问题,这里,我们让发送方负责检测和恢复丢包工作。假定发送方传输一个数据分组,该分组或者接收方对该分组的ACK丢失。在这两种情况下,发送方都收不到应当到来的接收方的响应。

如果发送方愿意等待足够长的时间以便确定分组已丢失,则它只需重传该数据分组即可。

基于时间的重传机制

发送方至少需要等待这样长的时间:即发送方与接收方之间的一个往返时延(可能会包括在中间路由器的缓冲时延)加上接收方处理一个分组所需的时间。

实践中采取的方法是发送方明智地选择一个时间值,以判定可能发生了丢包。如果在这个时间内没有收到ACK,则重传该分组。注意到如果一个分组经历了一个特别大的时延,发送方可能会重传该分组,即使该数据分组及其ACK都没有丢失。

这就是发送方到接收方的信道中引入了冗余数据分组的可能性。幸运的是,rdt 2.2协议已经有足够的功能(即序号)来处理冗余分组情况。


流水线可靠数据传输协议

停等方式存在性能问题,解决问题的一个方法是:允许发送方发送多个分组而无需等待确认。

因为许多从发送方向接收方输送的分组可以被看成是填充到一条流水线中,故这种技术被称为 流水线(pipelining)

流水线对可靠数据传输协议可带来如下影响:

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

回退N步 (Go-Back-N, GBN)

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

N常被称为窗口长度,GBN协议也常被称为 滑动窗口协议(sliding-window protocol)

为什么限制为N而不是无限制呢?后面我们知道流量控制是对发送方施加限制的原因之一。

在GBN协议中,对序号为n的分组的确认采取 累积确认 的方式,表明接收方已正确接收到序号为n的以前且包括n在内的所有分组。

协议的名字“回退N步”来源于出现丢失和时延过长分组时发送方的行为,如果出现超时,发送方重传所有已发送但还未被确认过的分组。

选择重传 (Selective Repeat, SR)

然而,GBN本身也有一些情况存在着性能问题。尤其是当窗口长度和带宽时延都很大时,在流水线中会有很多分组更是如此。单个分组的差错就能够引起GBN重传大量分组,许多分组根本没有必要重传。随着信道差错率的增加,流水线可能会被这些不必要重传的分组所充斥。

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

面向连接的运输: TCP

TCP连接提供的是全双工服务。

TCP 首部数据格式

RFC 793 TCP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
                                 
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-+
| Source Port | Destination Port |
+-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-+
| Sequence Number |
+-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-+
| Acknowledgment Number |
+-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-+
| Checksum | Urgent Pointer |
+-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-+
| Options | Padding |
+-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-+
| data |
+-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-+

TCP Header Format

Note that one tick mark represents one bit position.

  • 序号 :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。

  • 确认号 :期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。

  • 数据偏移 :指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。

  • 确认 ACK :当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。

  • 同步 SYN :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。

  • 终止 FIN :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。

  • 窗口 :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。

三次握手

假设 A 为客户端,B 为服务器端。

  • 首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。

  • A 向 B 发送连接请求报文,SYN=1,ACK=0,选择一个初始的序号 x。

  • B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。

  • A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。

  • B 收到 A 的确认后,连接建立。

三次握手的原因

第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。

客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。

四次挥手

以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。

  • A 发送连接释放报文,FIN=1。

  • B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。

  • 当 B 不再需要连接时,发送连接释放报文,FIN=1。

  • A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接。

  • B 收到 A 的确认后释放连接。

四次挥手的原因

客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。

TIME_WAIT

客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:

  • 确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。

  • 等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。

滑动窗口

窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。

发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。

接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为 {31, 34, 35},其中 {31} 按序到达,而 {34, 35} 就不是,因此只对字节 31 进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。

流量控制

流量控制是为了控制发送方发送速率,保证接收方来得及接收。

接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。

TCP拥塞控制

如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。

TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。

发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,注意拥塞窗口与发送方窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。

为了便于讨论,做如下假设:

  • 接收方有足够大的接收缓存,因此不会发生流量控制;
  • 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。

1. 慢开始与拥塞避免

发送的最初执行慢开始,令 cwnd = 1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 …

注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能性也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。

如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。

2. 快重传与快恢复

在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。

在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3

在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。

慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。