跳转至

传输层

传输层

基本概念

传输层与网络层的关系

传输层位于应用层和网络层之间,基于网络层向应用程序提供通信服务,运行在不同终端上的应用进程仿佛是直接连在一起的。其中,网络层为主机之间的通信提供服务,传输层为应用层进程之间的通信提供服务。

举个例子:设想在应用程序和网络之间存在一扇“门”。需要发送报文时,发送进程将报文推到门外,门外的运输设施(因特网)将报文送到接收进程的门口;需要接收报文时:接收进程打开门,即可收到报文。

在 TCP/IP 网络中,这扇“门”称为套接字 (socket),是应用层和传输层的接口,也是应用程序和网络之间的 API。如下图所示:

应用层与传输层之间的接口

传输层与网络层的区别:

  • 网络层提供「尽力而为」的服务:网络层尽最大努力在终端间交付分组,但不提供任何承诺,不保证交付,不保证按序交付,不保证数据完整,不保证延迟,不保证带宽等;
  • 传输层则是「有所为、有所不为」: 传输层可以通过差错恢复、重排序等手段提供可靠、按序的交付服务。但无法提供延迟保证、带宽保证等服务。

传输层的作用

传输层具体能做的:

  • 最低限度的传输服务:将「终端与终端」的数据交付扩展到「进程与进程」的数据交付,报文检错服务;
  • 增强服务:可靠数据传输,流量控制,拥塞控制。

传输层通过 UDP 协议和 TCP 协议完成上述任务,其中 UDP 协议仅能提供最低限度的传输服务, TCP 协议不仅能提供最低限度的传输服务,增强服务也能提供。使用 UDP 协议和 TCP 协议的典型应用及其对应的应用层协议如下图所示:

使用 UDP 和 TCP 的典型应用和应用层协议

复用与分用

传输层通过端口的概念实现了主机间信道的复用和分用。即:

  • 多路复用 (Multiplexing):发送方不同的应用进程都可以使用同一个传输层协议来传输数据;
  • 多路分解 (Demultiplexing):数据到达接收方时,接收方的传输层能够将其正确交付到目的应用进程。

约定一台主机的端口用 16 位来表示,那么就一共有 \(2^{16}-1=65535\) 个 端口号。主机上的端口号分类如下图所示:

端口号的分类

其中,熟知端口就是我们在使用网络进行通讯时常常会用到的端口,取值范围为 \([0,1023]\)。互联网数字分配机构 (Internet Assigned Numbers Authority, IANA) 把这些端口号指派给了 TCP/IP 最重要的一些应用进程。当一种新的应用程序出现后,IANA 必须为它指派一个熟知端口号,否则因特网上的其他应用进程就无法和它进行通信。常用的熟知端口如下图所示:

常用端口

Tip

从上面的介绍可以看出,网络环境中分布式进程通信涉及两个不同主机的进程,那么一个完整的进程通信就需要用一个五元组标识:协议、本地 IP 地址、本地端口号、远程 IP 地址、远程端口号。下面分别介绍两种传输层协议来完成这项进程通信的“艰巨”任务。

UDP 协议

用户数据报协议 (User Datagram Protocol, UDP) 是传输层协议的一种,支持数据交付、报文检错。

UDP 字段分析

UDP 的报文字段如下图所示:

UDP 的报文字段

如上图所示,UDP 一共有「首部」和「数据」两个字段,其中首部只有 8 个字节,具体地:

  • 源端口字段:存储源端口号,在需要对方回信时选用。不需要时可用全 0;
  • 目的端口字段:存储目的端口号,终点交付报文时必须使用(利用此字段可以实现复用和分用的功能);
  • 长度字段:存储整个 UDP 用户数据报的长度(字节数),最小值为 8(仅含首部的情况);
  • 校验和字段:存储校验和数据,检测 UDP 用户数据报在传输中是否有错,有错就直接丢弃。

其中校验和字段是用来检错的,一共 16 位,需要计算「伪首部、首部、数据」三部分之和并取反作为该字段的结果。传输结束后重新计算一般来判断是否出现了传输错误。计算示例如下图所示:

UDP 校验和字段计算示例

UDP 功能分析

功能如下图所示:

UDP 的功能

其中:

  • 发送方:UDP 对应用层交下来的报文,既不合并,也不拆分,按照样发送;
  • 接收方:UDP 对 IP 层交上来的 UDP 用户数据报,去除首部后就原封不动地交付上层的应用进程,一次交付一个完整的报文。

因此,应用程序必须选择合适大小的报文。否则:

  • 若报文太长,IP 层在传送时可能要进行分片,降低 IP 层的效率;
  • 若报文太短,会使 IP 数据报的首部的相对长度太大,降低 IP 层的效率。

需要 UDP 协议的几个原因:

  • 由于 UDP 无建立连接的延迟,也不限制发送速率(不进行拥塞控制和流量控制),应用可以尽可能快地发送报文;
  • 报头开销小;
  • 协议处理简单。

适合使用 UDP 协议的应用特点:

  • 容忍丢包但对延迟敏感的应用,如流媒体;
  • 以单次请求/响应为主的应用,如 DNS;
  • 多播与广播应用;
  • 若应用非要基于 UDP 进行可靠传输的话,由应用层实现可靠性。

TCP 协议

传输控制协议 (Transmission Control Protocol, TCP)。在 UDP 支持的数据交付、报文检错的基础之上,还支持可靠数据交付、流量控制、拥塞控制。

需要注意的是,TCP 是面向连接的传输层协议,每一条 TCP 连接都只能是一对一的,即两个交互终端只能分别用一个端口进行通信。

TCP 字段分析

TCP 的报文字段

如上图所示,TCP 同样只有「首部」和「数据」两个字段,其中首部有 20 个字节的固定部分,具体地:

  • 源端口和目的端口字段:与 UDP 一致,均占 2 个字节,不再赘述;
  • 序号 (sequence number, seq) 字段:占 4 字节。TCP 连接中传送的数据流中的每一个字节都编上一个序号。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。TCP 协议建立连接时,通信双方需要为第一个报文随机产生一个初始序号 ISN,双方的初始序号是不同的;
  • 确认号 (acknowledgment number, ack) 字段:占 4 个字节。采用捎带确认法 1 或累积确认法,指出收到对方的下一个 TCP 报文的数据的第一个字节的序号。例如,B 收到 A 发来的报文段,其序号为 501,数据长度为 200 字节,B 正确接收后期望收到的下一个数据序号是 701(字节确认的优点是即使确认丢失也不一定导致发送方重传);
  • 头部长度字段:占 1 个字节。它指出 TCP 报文的数据起始处距离 TCP 报文的起始处有多远,以 4 字节为单位;
  • 保留字段:占 6 位。保留为今后使用,但目前应置为 0;
  • 六个控制位字段:占 6 位。定义了 6 种不同的控制位,用于 TCP 连接建立与释放、流量控制以及数据传输过程的控制。具体地:
    1. 紧急位 URG:当 URG = 1 时,表明紧急指针字段有效,它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据);当 URG = 1,TCP 就把紧急数据插入到本报文数据的最前面;
    2. 确认位 ACK:只有当 ACK = 1 时确认号字段才有效。当 ACK = 0 时,确认号无效。TCP 规定,在建立连接后所有传送的报文段都必须将 ACK 置 1;
    3. 推送位 PSH:当两个应用程序进行交互通信时,一端的应用程序希望在键入一个命令后立即收到对方的响应,此时 PSH = 1,接收方 TCP 收到 PSH = 1 的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满了后再向上交付;
    4. 复位位 RST:当 RST = 1 时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立 TCP 连接;
    5. 同步位 SYN:同步 SYN = 1 表示这是一个连接请求/响应报文。SYN = 1 而 ACK = 0, 表示这是一个连接请求报文,SYN = 1 而 ACK = 1 表示连接响应报文;
    6. 终止位 FIN:用来释放一个连接。FIN = 1 表明此报文段的发送端的数据已发送完毕,并要求释放运输连接。
  • 检验和:占 2 个字节。检验和字段检验的范围包括「伪首部、首部、数据」三部分,其中伪首部与 UDP 中计算校验和的伪首部唯一的区别在于第 4 个字段的值应为 6;
  • 紧急指针字段:占 4 个字节,与 URG 位配合使用,指出在本报文段中紧急数据的字节数(紧急数据放在本报文段数据的最前面),紧急指针指出了紧急数据的末尾在报文段中的位置;
  • 选项字段:长度可变,最多占 40 个字节。TCP 最初只规定了一种选项,即最大段长度 (Maximum Segment Size, MSS) 选项,表示 TCP 报文段中数据字段的最大字节数。后来又增加了窗口扩大选项,占 3 个字节,其中有一个字节表示移位值 S,其作用是将 TCP 首部中的窗口位数增大到 16 + S,相当于把窗口值向左移动 S 位后获得实际的窗口大小;
  • 填充字段:这是为了使整个首部长度是 4 字节的整数倍。

TCP 功能分析 - 可靠传输

可靠传输的核心逻辑就一点:发送方需要知道接收方收到了哪些数据。对应的协议叫做自动请求重传 (Automatic Repeat reQuest, ARQ)。分为停止等待 ARQ 和 连续 ARQ 两种。

停止等待 ARQ 协议。这是最朴素的做法,即每发一个报文段就确认一下。如下图所示:

朴素可靠传输:A 发送完报文段 M1 之后就暂停发送,等待 B 的确认(ACK)。B 收到 M1 后向 A 发送 ACK。A 在收到了接收方对 M1 的确认后继续发送报文段 M2。以此循环知道所有数据全部发送完毕

这种方法的信道利用率很低,时间全花在往返时延上了,这里提到单纯为了引出概念。

连续 ARQ 协议。这是更高效的做法,即利用滑动窗口策略,流水线式的收发数据(停止等待 ARQ 协议的窗口大小就是 1)。如下图所示:

流水线可靠传输:在收到接收方(B)的确认之前,发送方(A)连续发出多个分组

从上图可以看出,TCP 连接的两个端点都有两个窗口:

  • 发送窗口 (senc window, cwnd),也称通知窗口:准备发送的数据和已发送但未收到确认的数据;
  • 接收窗口 (receive window, rwnd):按序到达但未被应用程序接收的数据、不按序到达的数据。

通过这种机制,不仅可以实现更高效的可靠传输,后续的流量控制和拥塞控制也都可以实现。那么连续 ARQ 相较于停止等待 ARQ,区别在哪呢?也就是重传的方法是什么样的呢?有两种方式:后退 N 重发 (Go-back-N Repeat, GBR) 和选择重发 (Selective Repeat, SR)。

GBR 就是从出问题的那一个报文段开始一整个重新发送:

后退 N 重发 (Go-back-N Repeat, GBR):从出问题的那一个报文段开始一整个重新发送

SR 就是只重发出问题的报文段:

选择重发 (Selective Repeat, SR):只重发出问题的报文段

GBR 的方式就不用关心重排的问题,而 SR 的方式就需要预先缓存已接收的报文段,后续收到重发的报文段之后按照序号字段的信息重排一下再向上传递。

需要注意的是,连续 ARQ 一般采用累积确认法,与停止等待 ARQ 采用的捎带确认法 1 不同,该法只会确认已接受到报文段编号的最大值的下一个序号。例如,假设需要发送 \(M_1,M_2,M_3\) 共三个 TCP 报文段,采用连续 ARQ 协议。接收方收到了 \(M_1\)\(M_3\)\(M_2\) 因为某种原因没有传过去,那么接收方在返回 ACK 消息时,不会发送 \(M_3\) 的下一个序号,而会发送 \(M_2\) 的下一个序号。

TCP 功能分析 - 流量控制

考虑到接收方的接收性能,窗口大小不能无限大,此时就需要对窗口大小进行一定的约束。具体地:TCP 接收端有一个接收缓存,接收端 TCP 将收到的数据放入接收缓存,应用进程从接收缓存中读数据(进入接收缓存的数据不一定被立即取走、取完),如果接收缓存中的数据未及时取走,后续到达的数据可能会因缓存溢出而丢失。流量控制就是让发送端 TCP 通过调节发送速率,不使接收端缓存溢出。

下面给出一个流量控制示例:

A 向 B 发送数据,MSS = 100 字节。在连接建立时,B 告诉 A 其接收窗口 rwnd = 400 字节

这种流量控制策略会遇到两个问题:死锁问题、低传输效率问题。

死锁问题。如下图所示:

B 处理好缓存中的数据后又向 A 申请更多数据,但如果此时的申请丢失了,B 在等 A 的新数据,A 又不知道 B 在等数据,就产生了死锁局面

为了解决这个死锁局面,弄一个计时器就可以了。具体地,设置一个持续计时器 (persistence timer),只要 TCP 连接的一方收到对方的零窗口通知,就启动该持续计时器。若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带 1 字节的数据),对方在确认这个探测报文段时给出当前窗口值:

  • 若窗口仍然是零,收到这个报文段的一方就重新设置持续计时器;
  • 若窗口不是零,则死锁的僵局就可以打破了。

其实本质上就是「构造」一个传输数据的假象。

低传输效率问题。如果发送方产生数据很慢或者接收方接收数据很慢,就会导致网络传输的效率下降,光顾着传首部了,数据字段很小。比如 TelNet 协议中需要确认字符传输,如果每次都只确认一个字符,那发送效率就会很低;同理还有接收方的糊涂窗口综合症问题,即每次 rwnd 从 0 到 1 就开始向发送方确认 rwnd = 1 的情况了,导致传来传去数据字段大小始终只有 1。

为了解决上述问题,最好的办法就是多攒点东西再发送,或者多腾点空间再响应。

TCP 功能分析 - 拥塞控制

与流量控制的引出类似,考虑到网络的传输性能,窗口大小不能无限大,此时就同样需要对窗口大小进行一定的约束。

网络拥塞 (Network Congestion) 的表现形式:大量分组短时间内进入网络,超出网络的处理能力,使得分组延迟增大,继续引起更多的重传,使得网络吞吐量不断下降,最终导致系统崩溃。

网络拥塞的产生原因:在某段时间内,网络资源的数量不能满足网络通信的需求,网络的吞吐量将随输入负荷的增大而下降。可以细分为以下几点:

  • 链路容量不足,资源分配不均衡;
  • 节点缓存容量太小,流量分布不均衡;
  • 处理机处理速率太慢;
  • 拥塞本身会进一步加剧拥塞。

为了解决网络拥塞情况,要么提升整个网络的能力(这一般不现实,因为总会出现更多流量涌入网络的情况),我们只能从根源上解决这个问题,也就是所谓的拥塞控制。拥塞控制的具体定义是:防止过多报文进入网络而造成路由器和链路过载。

注意:流量控制 \(\neq\) 拥塞控制。流量控制的重点是点对点链路的通信量的局部控制,而拥塞控制的重点是进入网络的报文总量的全局控制。流量控制可以很好地解决收发双方之间的通信速度的协调,但无法控制进入网络的总体流量。即使每对收发双方之间的流量是合适的,但只要进入网络的流量过大,也会造成网络拥塞。就好比你的消化速度是比不过你的进食速度的,你总不能一直吃吧。下面这张图很形象的分析了流量控制与拥塞控制的区别:

流量控制 vs. 拥塞控制

如上图所示,流量控制取决于收发双方的收发能力,这主要取决于终端的网卡和处理器性能;而拥塞控制取决于网络设施的吞吐能力,这主要取决于网络基建,不是我们能随意左右的。

拥塞控制的解决方法主要有两种:

  1. 开环控制:网络辅助的拥塞控制。路由器向端系统提供显式的反馈,例如:设置拥塞指示比特、给出发送速率指示。ATM、X.25 采用此类方法;
  2. 闭环控制:端到端拥塞控制。网络层不向端系统提供反馈,端系统通过观察丢包和延迟,自行推断拥塞的发生。TCP 采用此类方法,具体来说,TCP 采用闭环控制的方法,基于滑动窗口感知网络拥塞程度,从而限制发送速率。

TCP 的拥塞控制逻辑(均是对于发送方而言):

  1. 感知拥塞。发送方利用丢包事件感知拥塞,比如,丢包:发送端收到 3 个重复的 ACK、分组延迟增大:重传定时器超时;
  2. 限制速率:发送方维持一个拥塞窗口 (congestion window, cwnd) 限制已发送但未确认的数据量:\(\text{LastByteSent}-\text{LastByteAcked}<\text{cwnd}\),因此 cwnd 会随发送方感知的网络拥塞程度而变化。

因此,实际的发送窗口大小不仅取决于接收方发送窗口大小(流量控制),还取决于网络的拥塞状况(拥塞控制)。因此有:

\[ \text{发送窗口值} = \min (\text{接收方发送窗口值},\text{拥塞窗口值}) \]

TCP 的拥塞控制策略:慢开始 (slow start)、拥塞避免 (congestion avoidance)、快重传 (fast retransmit)、快恢复 (fast recovery)。

其中慢开始就是让 cwnd 从 1 开始倍增,但为了防止倍增几轮之后过大,就又设置了一个慢开始阈值 (slow-start threshold, ssthresh),这个阈值一般设置为当前发送窗口的一半。当 cwnd 没有超过 ssthresh 时就正常倍增,如果倍增后会超过 ssthresh,就使用拥塞避免算法,即逐个递增。下面这张图很好地解释了这两个算法:

慢开始算法与拥塞避免算法:假设发送方的发送窗口大小为 32,那么初始的 ssthresh 就是 16。刚开始 cwnd 从 1 开始倍增直到 16,如果继续倍增就会超过 ssthresh,此时开始执行拥塞避免算法,即逐个递增,直到遇到网络拥塞就将 cwnd 重新置 1 并调整 ssthresh 为发生网络拥塞时 cwnd 的一半

当然慢开始结合拥塞避免算法也是有缺点的,其会因为一个报文超时而简单地判断网络出现拥塞,这显然是不合理的。为此提出了快重传与快恢复算法。

所谓快重传,就是当接收端没有收到某个报文段时,立即向发送端连续发送三个重复的 ACK 确认,发送端在收到这三个连续的 ACK 确认后立刻重传缺失的报文段。同时启用快恢复算法,将 cwnd 减半。

快重传示意图:

快重传示意图

快恢复示意图:

快恢复示意图

TCP 功能分析 - 连接管理

经典的三次握手和四次挥手。前者建立 TCP 连接,后者释放 TCP 连接。

三次握手建立 TCP 连接

如上图所示,三次握手建立 TCP 连接主要分以下三个流程:

  1. 第一次握手:A 的 TCP 协议向 B 发出 TCP 连接请求报文,其首部中的同步位 SYN = 1,并随机产生发送序号 seq = x,表明传送数据时的第一个数据字节的序号是 x(TCP 规定 SYN = 1 的报文段不能携带数据,但要消耗掉一个序号);
  2. 第二次握手:B 的 TCP 协议收到 A 的 TCP 连接请求报文后,如同意建立 TCP 连接,则发回确认报文。B 在确认报文中应置 SYN = 1 且 ACK = 1,确认号 ack = x +1,随机产生发送序号 seq = y(这个报文段也不能携带数据,但同样要消耗掉一个序号);
  3. 第三次握手:A 收到 B 的确认报文后,也向 B 发出确认报文,其确认位 ACK = 1,发送序号为 seq = x+ 1 ,确认号 ack = y + 1。A 的 TCP 协议通知上层应用进程,与 B 的 TCP 连接已经建立。B 的 TCP 协议收到主机 A 的确认后,也通知其上层应用进程与 A 的 TCP 连接已经建立(TCP 规定 ACK 报文段可以携带数据,但如果不携带数据,则不消耗序号,下一个数据报文段的序号仍是 seq = x + 1)。

当客户进程与服务器进程之间的 TCP 连接建立后,双方的应用进程就可以使用这个连接进行全双工的字节流传输。为了防止 TCP 连接处于长时间空闲,服务器的 TCP 协议为所建立的每个 TCP 连接设置了一个保持计时器。当服务器收到客户端的报文时,立即将保持计时器复位。如果保持计时器超时,服务器没有收到客户端的报文,则每隔 75 秒向客户端发送 10 个探测报文。如果没有得到客户端的响应,则假设客户端出现故障,终止该 TCP 连接。

四次挥手释放 TCP 连接

如上图所示,四次挥手释放 TCP 连接主要分以下四个流程:

  1. 第一次挥手:数据传输结束后,通信的双方都可释放连接。假设 A 先向 B 发出连接释放报文主动关闭 TCP 连接。连接释放报文首部的 FIN = 1,其序号 seq = u(TCP 规定 FIN 报文段即使不携带数据,也消耗掉一个序号);
  2. 第二次挥手:B 发出确认,确认号 ack = u + 1,而这个报文段的序号 seq = v。TCP 服务器进程通知高层应用进程。A 到 B 这个方向的连接就释放了,TCP 连接处于半关闭状态。B 若发送数据,A 仍要接收;
  3. 第三次挥手:若 B 已经没有要向 A 发送的数据,则其也要向 A 发出连接释放请求报文;
  4. 第四次挥手:A 收到 B 的连接释放报文后,发出确认,在确认报文中 ACK = 1,确认号 ack = w + 1,序号 seq = u + 1。

为了保证 TCP 连接释放的正常进行,TCP 协议为每条 TCP 连接设置了时间等待计时器,时间为两个最长报文寿命 (Maximum Segment Lifetime, MSL),当四次挥手后,被动方必须等待 2MSL 时间后,TCP 连接才真正关闭。这是为了保证 TCP 协议的全双 ⼯ 连接能够可靠关闭,因为:

  1. 保证发送的最后一个 ACK 报文段能够到达 B;
  2. 防止「已失效的连接请求报文段」出现在本连接中。

  1. 在计算机通信中,当一个数据帧到达的时候,接收方并不是立即发送一个单独的控制帧,而是抑制一下自己并且开始等待,直到网络层传递给它下一个分组。然后,确认信息被附在往外发送的数据帧上(使用帧头中的 ACK 域)。实际上,确认报文搭了下一个外发数据帧的便车。这种“将确认暂时延迟以便可以钩到下一个外发数据帧”的技术称为捎带确认 (piggybacking)。捎带确认 | 百度百科 - (baike.baidu.com)