目录
1、传输层
2、UDP
3、可靠数据传输
4、TCP
正文
1、传输层
从宏观上来讲,传输层 (Transport Layer) 提供逻辑上的 进程间的 (process-to-process) 数据传输服务
从微观上来讲,传输层连接着应用层和网络层,具体来说包括以下两个作用:
- 在发送数据时,将来自应用层的消息 (message) 封装成数据段 (segment),然后传递给网络层
- 在接收数据时,将来自网络层的数据段 (segment) 拼接成消息 (message),然后传递给应用层
多路复用 (Multiplexing) 和多路分解 (Demultiplexing) 和上面的两个过程类似,但侧重点有所不同
- 多路复用:在发送方,接收来自不同套接字的消息,并添加上头部字段(用于多路分解)
- 多路分解:在接收方,解析头部字段,将信息分发给对应的套接字
多路复用和多路分解的关键问题在于怎么将不同的数据段和不同的套接字对应起来?
这里要求数据段要有 特殊字段,套接字要有 唯一标识,并且能够根据特殊字段找到唯一标识
数据段的特殊字段,其实就是某些头部字段,具体来说就是 IP address 和 port number
- IP address:可以用于定位主机,在网络层处理,发送时封装到数据,接收时从数据提取
- port number:可以用于定位进程,在传输层处理,发送时封装到数据,接收时从数据提取
套接字的唯一标识,是在套接字建立的时候,根据发送方和接收方的状态确定的,这里分为两种情况
- 如果发送方和接收方是无连接的(UDP),那此时套接字的标识是一个二元组
(destination IP address, destination port number)
- 如果发送方和接收方是有连接的(TCP),那此时套接字的标识是一个四元组
(source IP address, destination IP address, source port number, destination port number)
好,最后我们简单概括一下多路复用和多路分解的整个过程
对于发送方而言(多路复用)
传输层接收来自应用层的消息,通过添加 port number 等头部字段将消息封装成数据段
网络层接收来自传输层的数据段,通过添加 IP address 等头部字段将数据段封装成数据报
对于接收方而言(多路分解)
网络层从链路层接收数据报,根据 IP address 判断是否应该向上传递(当前主机是否应该接收数据)
传输层从网络层接收数据段,根据 IP address 和 port number 判断应该传递给哪个套接字
2、UDP
(1)UDP 是什么
用户数据报协议 (User Datagram Protocol, UDP) 是一种 不可靠、无连接 的数据传输协议
- 不可靠:数据有可能会丢失,而且多个数据到达目的地时可能是没有顺序的
- 无连接:发送方和接收方之间无需建立连接,每个发出的数据段都是独立的
UDP 只是负责把数据丢到网络,交给下层进行传输,至于数据能不能到达、到达后顺序有没有错,它都一律不管
既然 UDP 这么不可靠,那我们为什么还要使用 UDP?一个字,快
相比于 TCP 使用一整套复杂的控制流程保证传输可靠,UDP 牺牲可靠性提高数据的传输速度
(2)UDP 数据段格式
我们可以看到,传输层数据段其实是对应用层消息的封装,而在 UDP 协议就是加上四个头部字段而已
- source port number:发送方的端口号
- destination port number:接收方的端口号
- length:数据段的长度,包括头部字段,以 byte 为单位
- checksum:校验和,用于检查数据是否正确
3、可靠数据传输
刚才提到,UDP 它是一种不可靠的数据传输服务,那么自然也会有可靠的数据传输服务
怎么样才算是可靠的呢?就是能够保证接收方收到的数据:不会重复、不会丢失、没有错误、顺序正确
由于底层的数据传输通道是不可靠的,通常会发生位错误和包丢失,因此导致可靠数据传输协议十分复杂
可靠数据传输协议大体可以分为两种类型,一种是停等协议 (stop-and-wait),一种是流水线协议 (pipeline)
- 停等协议:发送方一次只能发送一个包,等待接收方返回响应后,才会发送下一个包,主要包括 Rdt 各版本
- 流水线协议:发送方可以同时发送多个包,主要介绍 GBN 和 SR 两种协议
(1)Rdt1.0
Rdt1.0 假设传输管道是可靠的,也就是说在传输过程中不会发生位错误,也不会发生包丢失
由于传输管道是可靠的,所以整个过程十分简单,发送方只要负责发送包,接收方只要负责接收包就可以了
(2)Rdt2.0
Rdt2.0 假设在传输过程中可能会发生位错误,但不会发生包丢失
若在传输过程中,包会发生位错误,那么发送方并不知道接收方收到的包是不是正确的
所以当接收方收到一个包后,应该返回一个 响应 (ACK 或 NAK) 给发送方,告诉它收到的包是正确的还是错误的
如果是正确的,那么发送方可以发送下一个包,万一是错误的,那么发送方应该重发上一个包
对于接收方而言
如果收到一个正确的包,那么把包传到上层,然后给发送方发送一个 ACK,告诉它收到的包是正确的
如果收到一个错误的包,那么直接把包丢弃,然后给发送方发送一个 NAK,告诉它收到的包是错误的
对于发送方而言
如果收到 ACK ,那么发送下一个包给接收方
如果收到 NAK,那么重发上一个包给接收方
其中 ACK 和 NAK 就是接收方返回给发送方的响应,用来告诉发送方,收到的包是不是正确的
- ACK:acknowledgement,接收方显式告诉发送方 “ 我收到正确的包,你可以发送下一个包 ”
- NAK:negative acknowledgement,接收方显式告诉发送方 “ 我收到错误的包,你需要重发上一个包 ”
Rdt2.0 看似完美,但其实它有一个致命的错误,假如 ACK 或者 NAK 发生错误,那该怎么办呢?
此时,发送方不能发送下一个包,因为假如上一个包是错误的,接收方就会缺少了一个包
而且发送方也不能重发上一个包,因为假如上一个包是正确的,接收方就会收到重复的包
(3)Rdt2.1
Rdt2.1 假设在传输过程中可能会发生位错误,但不会发生包丢失,并且考虑 ACK 或者 NAK 发生错误的情况
下面我们来看看 Rdt2.1 是怎么解决 ACK/NAK 发生错误的情况的,以及它对比于 Rdt2.0 会做出什么样的改动
首先规定:如果发送方收到错误的 ACK/NAK,那就重发上一个包
那么接下来要解决的问题就是:接收方怎么知道现在收到的包是不是和上一个包是一样的
如果是一样的,那就说明接收方已经收到上一个包,所以现在收到的包是重复的,直接把包丢弃就好
如果是不同的,那就说明接收方没有收到上一个包,所以现在收到的包是合适的,然后需要把包接收
方法也很简单,只需要发送方给每个发送的包添加 交替的序列号 (sequence number),就能区分连续的两个包
比如第一个包的序列号为 0,那么第二个包的序列号应该为 1,然后第三个包的序列号为 0,以此类推
对于接收方而言
如果收到一个正确的包,并且序列号和上一个传到上层的包不同,那么把包传到上层,给发送方发送一个 ACK
如果收到一个正确的包,但是序列号和上一个传到上层的包相同,那么直接把包丢弃,给发送方发送一个 ACK
如果收到一个错误的包,直接把包丢弃,然后给发送方发送一个 NAK
对于发送方而言
如果收到正确的 ACK ,那么发送下一个包给接收方
如果收到正确的 NAK,那么重发上一个包给接收方
如果收到错误的 ACK/NAK,重发上一个包给接收方
Rdt2.1 已经可以解决假设中的情形,但是能不能再优化呢?
答案是肯定的,我们可以 只使用 ACK 而不使用 NAK 达到一样的效果
(4)Rdt2.2
Rdt2.2 假设在传输过程中可能会发生位错误,但不会发生包丢失,并且仅仅使用 ACK 而不使用 NAK
Rdt2.2 规定接收方收到一个包,无论包正确与否,都会返回一个 ACK,并且带上最后一个传到上层的包的序列号
对于接收方而言
若收到一个正确的包,并且序列号不同,则把包传到上层,返回 ACK,带上最后一个传到上层的包的序列号
若收到一个正确的包,但是序列号相同,则也会把包丢弃,返回 ACK,带上最后一个传到上层的包的序列号
如果收到一个错误的包,直接把包丢弃,但是还是返回一个 ACK,带上最后一个传到上层的包的序列号
对于发送方而言:
如果收到正确的 ACK,并且现在收到的序列号和上一个收到的序列号不同,那么发送下一个包
如果收到正确的 ACK,但是现在收到的序列号和上一个收到的序列号相同,那么重发上一个包
如果收到错误的 ACK,重发上一个包
(5)Rdt3.0
Rdt3.0 假设在传输过程中不仅会发生位错误,而且会发生包丢失和延迟,这就是真实的网络环境
这种情况其实也很容易处理,只需要发送方给每个发送的包设置 超时时间 (timeout) 即可
假如在规定时间内,发送方没有收到对应发出的包的响应 (ACK),那就重发对应的包
由于整个过程比较复杂,所以在这里画两个 有限状态机 方便大家理解发送方和接收方的状态转移情况
发送方的有限状态机如下(注意,这里 ACK 和 PKT 后的数字代表带有的序列号):
接收方的有限状态机如下(注意,这里 ACK 和 PKT 后的数字代表带有的序列号):
最后给大家举两个例子,一个是对应包丢失的情况,一个是对应包延迟的情况
(6)GBN
回退 N 步 (Go-Back-N, GBN),它是流水线协议的一种,允许发送方同时发送多个包
相比 Rdt3.0,GBN 使用 k-bit 序列号 以及添加了 发送窗口 的概念,发送窗口的大小必须小于等于 2k - 1
由于在发送过程中,发送窗口会不断向前移动,因此 GBN 又被称为滑动窗口协议 (sliding window protocol)
我们先从接收方的角度来看,由于接收方没有缓存,所以要求接收方收到的包都是要按照顺序的
如果接收方收到的包顺序错误,那么直接把包丢弃,如果接收方收到的包顺序正确,那么把包传到上层
什么叫按照顺序呢?也和之前一样,接收方通过序列号判断收到的包顺序是否正确
比如说,现在上一个收到的包序列号为 1,那么下一个期望收到的包序列号应该为 2,以此类推
概括来说,接收方的动作有以下三条规则:
- 如果收到的包顺序正确,那么把包传到上层,发送 ACK,带上期望收到的包的序列号
- 如果收到的包顺序错误,那么直接把包丢弃,发送 ACK,带上期望收到的包的序列号
- 如果收到的包是错误的,也是直接把包丢弃,发送 ACK,带上期望收到的包的序列号
我们再从发送方的角度来看,由于允许同时发送多个包并添加了发送窗口的概念,所以稍微有点复杂
发送方如果收到上层调用要求发包,首先会检查发送窗口是否已满,如果满了先不发送,如果未满那就发送
如果发送方收到 ACK(n),表明接收方已经成功收到所有序列号小于等于 n 的包,则发送方就可以向前移动窗口
如果发生超时,发送方会认为接收方没收到当前分组的包,然后重发所有【已经发出,等待接收 ACK】的包
概括来说,发送方的动作也是有三条规则:
- 如果收到上层调用,并且窗口未满 (next < base + N),那么就发送下一个将要发送的包 PKT[next]
- 如果收到 ACK(n), 并且序列号 n 在发送窗口范围内,那就向前移动窗口使得 base 等于 n + 1
- 如果 PKT(n) 超时,重发所有等待接收 ACK 的包,即 PKT[base] ~ PKT[next - 1]
上面的描述可能会有点抽象,我们直接来看一个例子吧
(7)SR
选择重传 (Selective Repeat, SR) 在 GBN 的基础上做了改进,相比于 GBN,SR 添加了 接收窗口 的概念
接收窗口其实是一块缓存,当收到不按顺序到达的包时,可以先放缓存,等前面的包到达后再一起交给上层
在 SR 协议中,发送窗口的大小必须小于等于 2k-1,同时接收窗口的大小必须小于等于 2k-1
我们先从接收方的角度来看,由于接收方拥有一块缓存,所以能够先把不按顺序到达的包放在缓存
当缓存中有按照顺序的包时,把这些按照顺序的包一起交给上层,然后向前移动窗口
比如,缓存中已经有 2、3、5,如果现在收到 1,那么就可以把 1、2、3 一起交给上层,然后窗口移动到 4
概括来说,接收方的动作有以下三条规则:
- 如果收到 PKT(n) 并且序列号 n 在接收窗口范围内,那么把包放在缓存,然后返回一个 ACK(n)
- 此时检查接收窗口,若有顺序正确的包,那么把这些包交给上层,然后向前移动窗口
- 如果收到 PKT(n) 但是序列号 n 不在窗口的范围内,那就什么都不要做
- 收到错误的 PKT,那也什么都不要做
我们再从发送方的角度来看,当发送方收到 ACK(n) 时,会先把序列号为 n 的包标记起来
如果在接收窗口始端中出现连续的被标记的包时,就会向前移动发送窗口,直至遇到未被确认的包
比如,窗口中 2、3、5 是标记为被确认的,如果现在 1 也被标记,那么就可以将窗口移动到 4
概括来说,发送方的动作也是有三条规则:
- 如果收到上层调用,并且窗口未满 (next < base + N),那么就发送下一个将要发送的包 PKT[next]
- 如果收到 ACK(n) 并且序列号 n 在发送窗口范围内,那么标记这个包为被确认的
- 此时检查发送窗口,若窗口始端有连续确认的包,那么向前移动窗口
- 如果 PKT(n) 超时,重发 PKT(n)
最后来看一个例子哈