传输层
概述
传输层的协议有 UDP 和 TCP,基本能解决 99%以上的问题,所以不需要有第三个协议。在了解 TCP 和 UDP 之前,我们需要先了解计算机网络中可靠的演进。
可靠性传输 RDT(Reliable Data Transfer)
数据的可靠传输是计算机网络中的通用概念,也是 UDP 和 TCP 的基石。计算机五层模型中,上层需要借助下层提供的功能来完成数据传输,那么如果下层是不可靠,我们该如何保证数据的可靠传输?接下来会一步步假设,一步步的暴露问题,来看看可靠性传输 RDT 是如何演进的?
RDT1.0
对应场景:下层的信道是完全可靠的,没有比特差错,没有分组丢失
发送方和接收方的 FSM:发送方将数据发送到下层信道,接收方从下层信道接收数据,rdt1.0 的工作就是
封装和解封装
RDT2.0
对应场景:下层信道可能出现差错,将分组中的比特翻转,0 变成 1,1 变成 0
应对手段:
- 校验和:用校验和的方式进行差错检测
- 确认 ACK:接收方明显的告诉发送方分组已经被接收
- 否定确认 NAK:接收方明显地告诉发送方分组出现差错(发送方接收到 NAK 后,会重传分组)
过程描述:发送方发送 packet 到下层信道,接收方接收,通过校验和发送数据有差错,返回 NAK,发送方再次发送原 packet,所以 rdt2.0 新机制控制编码进行差错校验。
存在的问题:发送 packet 可能会出现错误,但是返回的 ACK/NAK 是不是就一定是正确的呢?答案是肯定是否定的,但是发送方怎么做呢?重传?可能会重复。不重传?可能会造成死锁或出错。所以引入了 rdt2.1。
RDT2.1
对应场景:2.0 中接收方返回的 ack/nack 有可能出错,如果重传数据,还有可能重复。
对应手段:
- 加上序号:给每个 packet 加上序号,哪怕客户端接收到重复数据,也能判断到重复。
- 停止等待:发送方只发送一个分组,然后等待接收方的应答的方式称为停止等待协议。
RDT2.1(无 NACK)
对应场景:发送方需要识别 ACK 和 NACK 两套状态管理,太复杂了
对应手段:
- 取消 NACK:取消 NACK 的方式,那么接收方怎么告诉发送方数据错了呢?可以通过返回的 ACK+上一个 packet 的序号方式返回。
- 重复 ACK:当接收方收到重复数据的 ACK 时,说明下一个发送失败了。接收方会重发当前 ACK 序号后的报文
存在的问题:返回的 ACK 也有可能丢失,发送方就会一直等待确认,所以引入了 rdt3.0
RDT3.0
对应场景:发送方一直等待 ACK 但是 ACK 有可能丢失。
对应手段:
- 定时器:增加一个定时器,对应时间段内没收到 ACK 就一直触发超时重传机制。
存在问题:RDT3.0 以及以前,一直采用停止等待协议,也就是一个包没收到响应就不会发送下一个,信道利用率太低。由此引入了流水线协议。
停止等待协议
先明白一个概念 WS(window size)代表可发送,或者可接收的窗口的长度。在停止等待协议中,发送方只能发送一个分组,等待确认后,再发送下一个,所以。
发送方窗口=1
接收方窗口=1
流水线协议
为了提高信道的利用率,我们需要能够批量发送分组。当发送方窗口>1,我们称之为流水线协议。

接收方窗口=1,方送方窗口=N。
- 重传定时器:发送方只为最早的未经确认的分组维护一个定时器,如果产生超时,将重新发送该分组后的所有分组,也就是顾名思义的回退 N 步。
- 丢弃乱序:接收窗口只有 1,如果接收到比较大的序号分组,都会选择丢弃。

接收方窗口=N,发送方窗口=N。
- :发送方为每个分组都设置了重传定时器,如果触发重传,只需要传某个分组即可。
- :接收方可以乱序接收分组,当最头部分组整体接收完毕,滑动窗口可以整体后移,并将头部接收到的多个分组提交给上层。
流水线协议总结
Go-Back-N:
- 发送端最多在流水线中有 N 个未确认的分组
- 接收端只是发送累计型确认 cumulative ack
- 接收端如果发现gap不确认新到来的分组
- 发送端拥有对最老的未确认分组的定时器、
- 只需设置一个定时器
- 当定时器到时时,重传所有未确认分组
Selective Repeat:
- 发送端最多在流水线中有 N 个未确认的分组
- 接收方对每个到来的分组单独确认 individual ack(非累计确认)
- 发送方为每个未确认的分组保持一个定时器
- 当超时定时器到时,只是重发到时的未确实分组
TCP
报文结构

- URG:紧急指针
- ACK:是否确认应答消息
- PSH:是否立即从缓存区取走数据
- RST:复位标识
- SYN:同步序列号标识(TCP 连接时使用)
- FIN:结束序列号标识(TCP 断开时使用)
- 16 位窗口----不是滑动窗口的大小,滑动窗口的大小是固定的,他是同来记录缓冲区的大小,如果大小为 0,就不会发送数据了。以此达到流量控制。
- 关于序号 seq 和确认号 ack,我们可以展开说说。seq 的值代表当前报文的发送序号,ack 的值代表接收方需要的数据将从对方哪个序号开始。
- 所以响应 ACK=请求 SEQ+请求字节数。

捎带技术
TCP 是全双工的,在接收方返回 ACK 的时候,同时还需要向对方传输一些数据,这个时候可以将数据和 ACK 报文合并成一个分组,称之为。
可靠性与顺序传输
TCP 是一种流水线的传输方式,那么 TCP 属于 GBN 还是属于 SR 呢,其实TCP 是 GBN 和 SR 的混合体
- :因为有了分组编号,接收方收到乱序的分组后,不会选择丢弃,而是缓存起来,排好序。返回的 ACK 仍然是第一个未收到的分组序号。
- :tcp 发送方只为最早的分组设置一个定时器,当分组未收到 ACK 触发超时重传,并获得 ACK 应答后,才开始为下一个未确认号分组开始计时。
- :基于接收方 ACK 确认机制,ACK 序号为按顺序第一个未收的分组,发送方接收到三次相同的 ACK 序号后,会触发快速重传机制,重传 ACK 序号标识的分组。
- :发送方未收到分组的 ACK 时,只会重新发送单个分组,而不会发送所有分组。
流量控制
如果接收方的接受速率不足,发送方就应该减缓发送速率,避免接收方溢出。发送速度的控制是通过发送的滑动窗口大小来控制的。
通过上面的报文,我们能看见 16 位窗口大小。这个窗口,就是接收方维护的一块变量,值代表接收方缓冲区还剩多少可用的缓存空间。
拥赛控制
有时,发送方和接收方性能后很好,结果是中途的网络带宽不行,网络中堵塞了。这个时候如果还是依然大量的发送消息,反而会造成更大面积的网络堵塞。
如何感知网络拥塞?我们可以通过丢包率来判断,根据对应的公式,算出网络拥塞下的发送速率该有多大,进而求出当前滑动窗口的大小。
TCP 的连接速率是一个慢启动的过程,
- 一个开始滑动窗口只有 1。
- 每一次收到 ACK,都会城倍的增加滑动窗口的大小
- 每一次快速重试都会降为一半。
- 每一次超时重传,都会降为 1
联合控制: 滑动窗口是由流量控制来拥堵控制来共同决定的。WS=min(拥堵窗口,流量控制窗口)
连接管理
到了我们最感兴趣的八股文环节,来说说 TCP 的三次握手和四次挥手。
三次握手

- SYN:连接请求/接收报文段
- seq:发送的第一个字节的序号
- ACK:确认报文段
- ack:确认号。希望收到的下一个数据的第一个字节的序号
刚开始客户端处于Closed的状态,而服务端处于Listen状态:
- Closed:没有任何连接状态
- Listen:监听来自远方 TCP 端口的连接请求
一、第一次握手:
客户端向服务端发送一个 SYN 报文(SYN=1),并指明客户端的序列号 ISN(x),即图中的 seq=x,表示报文段发送的数据的第一个字节的序号。此时客户端处于 SYN_Send 状态。
SYN-Sent:在发送连接请求后等待匹配的连接请求
二、第二次握手:
服务器收到客户端的 SYN 报文之后,会发送 SYN 报文作为应答(SYN=1),并且指定自己的初始化序列号 ISN(y),即图中的 seq=y。同时会把客户端的 ISN+1 作为确认号 ack 的值,表示已经收到客户端发来的 SYN 报文,希望收到的下一个数据的第一个字节序号是 x+1,此时服务器处于 SYN_Revd 的状态
SYN-Received:接收到和发送一个连接请求后等待对连接请求的确认
三、第三次握手:
客户端收到服务器响应的 SYN 报文之后,会发送一个 ACK 报文,也是一样把服务器的 ISN+1 作为 ack 的值,表示已经收到了服务端发来的 SYN 报文,希望收到的下一个数据的第一个字节的序号是 y+1,并指明此时客户端的序列号 seq=x+1(初始为 seq=x,所以第二个报文段要+1),此时客户端处于 Establised 状态。
服务器接收到 ACK 报文之后,也处于 Establised 状态,至此,双方建立起了 TCP 连接。
Established:代表一个打开的连接,数据可以传送给用户
为什么要三次握手?
三次握手的本质是,双方都要发出连接请求,交换自己的序列 seq,并确认对方能够正常接收做出应答。本来两个往返需要四次,服务这边将应答和请求合并了,所以只需要三次。
两次握手行不行?
两次握手相当于省略了第三次应答,服务器没法确认对方收到了自己的序列号。本来服务器在接收到了对方第一次请求进去半开状态,还没有建立连接。两次握手意味着接收到对方请求后,服务器就直接进入了连接状态,能直接发送数据。这时候对方没有接收到第二次的应答,没有协商好 seq 序号就发送消息,会产生错乱。
第三次握手丢失了会怎样?
服务器在发出第二次握手后,会进入半开 SYN_Revd 状态。等待第三次握手,如果第三次握手没有达到,会触发超时重传。例如间隔时间为 1s、2s、4s、8s......
SYN 洪泛攻击?
SYN 攻击就是Client 在短时间内伪造大量不存在的 IP 地址,并向 Server 不断的发送 SYN 包,Server 则回复确认包,并等待 Client 确认,由于源地址不存在,因此 Server 需要不断发直至超时,这些伪造的 SYN 包将长时间占用半连接队列,导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络拥塞甚至瘫痪。
四次挥手

回顾一下上图中符号的意思:
- FIN:连接终止位
- seq:发送的第一个字节的序号
- ACK:确认报文段
- ack:确认号,希望收到的下一个数据的第一个字节的序号
刚开始双方都处于 Established 状态,假设是客户端发起关闭请求。四次挥手的过程如下:
第一次挥手:
客户端发送一个 FIN 报文(请求连接终止:FIN=1),报文中会指定一个序列号 seq=u。并停止再发送数据,主动关闭 TCP 连接。此时客户端处于 FIN_WAIT1 状态,等待服务端的确认。
FIN-WAIT-1:等待远程 TCP 的连接中断请求,或先前的连接中断请求的确认。
第二次挥手:
服务端接收到 FIN 之后,会发送 ACK 报文,且把客户端的序号值+1 作为 ACK 报文的序列号值,表明已经接收到客户端的报文了,此时服务端处于 Close_Wait 状态。
Close_Wait:等待从本地用户发来的连接中断请求。
此时的 TCP 处于半关闭状态,客户端到服务端的连接释放。客户端收道服务端的确认后,进入 FIN_Wait2(终止等待 2)状态,等待服务端发出连接释放报文段。
FIN_Wait-2:从远程 TCP 等待中断请求。
第三次挥手:
如果服务端也想断开连接(没有要向客户端发出数据),客户端的第一次挥手一样,发送 FIN 报文,并指定一个序列号。此时服务端处于 Last_ACK 的状态,等客户端确认。
Last_ACK:等待原来发向远程 TCP 的连接中断请求的确认。
第四次挥手:
客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答(ack=w+1),且把服务端的序列值+1 作为自己 ACK 报文的序号值(seq=u+1),此时的客户端处于 Time_Wait**(时间等待)状态**
Time-Wait:等待足够的时间以确保远程 TCP 接收到连接中断请求的确认。
注意
这个时候由路由服务端到客户端的 TCP 连接未释放掉,需要经过时间等待计时器设置的时间 2MSL(一个报文的来时间)后进入 Closed 状态(这样做的目的是确保服务端收到自己的 ACK 报文。如果服务端的规定时间没有收到客户端发来的 ACK 报文的话,服务端会重新发送 FIN 报文给客户端,客户端再次收到 FIN 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文给服务端)。服务端收到报文之后,就关闭连接了,处于 Closed 状态。
为什么要四次挥手?
由于 TCP 的半关闭(haf-close)特性,TCP 提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。
任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。
通俗的来说,两次握手就可以释放一端到另一端的 TCP 连接,完全释放连接一共需要四次握手。
或者这么理解,其实建立连接和释放连接都需要四次。双方的请求加双方的应答。但是建立连接可以合并应答和请求。而释放连接,可能服务器还有需要发送的数据,没法合并,只能先应答,发完剩下的组后,再请求关闭。
UDP
UDP 首部的各字段如图 11-2 所示。

- UDP 长度,总共 16 位占两个字节。
- UDP 报文长度=UDP 报头(首部)+UDP 载荷。
- 该字段保存了 UDP 报文的长度,单位为字节。
- 2 个字节能表示的数据范围是 0~65535,也就是能的够表示的报文长度是 65536 字节(Byte)转换成 KB,65536/1024=64KB 这就是一个 UDP 报文所能表示的最大长度。
tcp 和 udp 差别
| TCP | UDP |
|---|---|
| 面向连接 | 无连接 |
| 可靠的 | 尽最大努力交付 |
| 有序 | 无序 |
| 速度慢 | 速度快 |
| 重量级 | 轻量级 |
| 适合对可靠性要求高的 | 适合电话,视频,直播 |
| 特性 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接(需三次握手建立连接) | 无连接 |
| 可靠性 | 可靠传输(确认、重传、排序、去重) | 不可靠传输(不保证数据到达或顺序) |
| 流量控制 | 支持(滑动窗口机制) | 不支持 |
| 拥塞控制 | 支持(慢启动、拥塞避免等算法) | 不支持 |
| 数据边界 | 无边界(字节流模式) | 有边界(数据报模式) |
| 头部大小 | 20~60 字节(复杂字段) | 固定 8 字节(轻量级) |
| 传输效率 | 较低(延迟高,开销大) | 较高(延迟低,开销小) |
| 传输模式 | 仅单播(一对一) | 支持单播、广播、多播 |
| 典型应用场景 | HTTP、FTP、电子邮件、数据库 | 视频流、在线游戏、DNS、IoT、VoIP |
| 适用场景优先级 | 数据完整性 > 实时性 | 实时性 > 数据完整性 |

