# TCP/UDP

{% hint style="info" %}
推荐阅读：小林 coding - [100 张图带你彻底搞懂 TCP 面试题](https://blog.csdn.net/qq_34827674/article/details/115861934)
{% endhint %}

## 1. TCP

### 1.1 三次握手

![](/files/-M_iZdl1ViPMZIS2J4MF)

第一次握手

* SYN = 1， seq(client) = x
* 客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后，客户端便进入 SYN-SENT 状态。

第二次握手

* SYN = 1，ACK = 1，确认序号 = x+1, seq(server) = y
* 服务端收到连接请求报文段后，如果同意连接，则会发送一个应答，该应答中也会包含自身的数据通讯初始序号，发送完成后便进入 SYN-RECEIVED 状态

第三次握手

* ACK = 1，确认序号 = y+1, seq(client) = x + 1
* 客户端收到连接同意的应答后，还要向服务端发送一个确认报文。客户端发完这个报文段后便进入ESTABLISHED 状态，服务端收到这个应答后也进入 ESTABLISHED 状态，此时连接建立成功。

{% hint style="info" %}
问题：**为什么不用两次握手？**

主要是为了防止已经失效的连接请求报文突然又传送到了服务器，从而产生错误。假设有这样一种场景, 客户端发送的第一个请求连接并且没有丢失，但是被滞留的时间太长。由于TCP的客户端迟迟没有收到确认报文，以为服务器没有收到，此时重新向服务器发送报文。 而现在第一个请求到达服务端，这个请求已经报废了，但是又会建立连接。如果采用的是三次握手，就算是那一次失效的报文传送过来了，服务端接受到了那条失效报文并且回复了确认报文，但是客户端不会再次发出确认。由于服务器收不到确认，就知道客户端并没有请求连接。
{% endhint %}

### 1.2 四次挥手

![](/files/-M_iZdl2KEU4ZWrCUIjI)

第一次挥手

* 若客户端 A 认为数据发送完成，则它需要向服务端 B 发送连接释放请求。

第二次挥手

* B 收到连接释放请求后，会告诉应用层要释放 TCP 链接。然后会发送 ACK 包，并进入 **CLOSE\_WAIT** 状态，表示 A 到 B 的连接已经释放，不接收 A 发的数据了。但是因为 **TCP 连接时双向的**，所以 B 仍旧可以发送数据给 A。

第三次挥手

* B 如果此时还有没发完的数据会继续发送，完毕后会向 A 发送连接释放请求，然后 B 便进入**LAST-ACK**状态。
* PS：通过延迟确认的技术（通常有时间限制，否则对方会误认为需要重传），可以将第二次和第三次握手合并，延迟 ACK 包的发送。

第四次挥手

* A 收到释放请求后，向 B 发送确认应答，此时 A 进入 **TIME-WAIT** 状态。该状态会持续 2MSL（最大段生存期，指报文段在网络中生存的时间，超时会被抛弃） 时间，若该时间段内没有 B 的重发请求的话，就进入 **CLOSED** 状态。当 B 收到确认应答后，也便进入 CLOSED 状态。

{% hint style="info" %}
问题：**为什么 A 要进入 TIME-WAIT 状态，等待 2MSL 时间后才进入 CLOSED 状态？**

为了保证 B 能收到 A 的确认应答。若 A 发完确认应答后直接进入 CLOSED 状态，如果确认应答因为网络问题一直没有到达，那么会造成 B 不能正常关闭。
{% endhint %}

{% hint style="info" %}
问题：**为什么建立连接是三次握手，关闭连接确是四次挥手呢？**

建立连接的时候， 服务器在LISTEN状态下，收到建立连接请求的SYN报文后，把ACK和SYN放在一个报文里发送给客户端。 而关闭连接时，服务器收到对方的FIN报文时，仅仅表示对方不再发送数据了但是还能接收数据，而自己也未必全部数据都发送给对方了
{% endhint %}

### 1.3 超时重传协议

#### 1.3.1 停止等待 ARQ

**正常传输过程**

只要 A 向 B 发送一段报文，都要停止发送并启动一个定时器，等待对端回应，在定时器时间内接收到对端应答就取消定时器并发送下一段报文。

**当报文丢失或出错**

报文传输的过程中丢包， 这时候超过定时器设定的时间就会再次发送丢包的数据直到对端响应，所以需要每次都备份发送的数据。传输过程中报文出错， 对端会抛弃该报文并等待 A 端重传。

**ACK 超时或丢失**

对端传输的应答也可能出现丢失或超时的情况。那么超过定时器时间 A 端照样会重传报文。这时候 B 端收到相同序号的报文会丢弃该报文并重传应答，直到 A 端发送下一个序号的报文。

#### 1.3.2 连续 ARQ

在连续 ARQ 中，发送端拥有一个发送窗口，可以在没有收到应答的情况下持续发送窗口内的数据，这样相比停止等待 ARQ 协议来说减少了等待时间，提高了效率。

**累计确认**

连续 ARQ 中，接收端会持续不断收到报文。如果和停止等待 ARQ 中接收一个报文就发送一个应答一样，就太浪费资源了。通过累计确认，可以在收到多个报文以后统一回复一个应答报文。报文中的 ACK 可以用来告诉发送端这个序号之前的数据已经全部接收到了，下次请发送这个序号 + 1的数据。但是累计确认也有一个弊端。在连续接收报文时，可能会遇到接收到序号 5 的报文后，并未接到序号 6 的报文，然而序号 7 以后的报文已经接收。遇到这种情况时，ACK 只能回复 6，这样会造成发送端重复发送数据，这种情况下可以通过 Sack 来解决。

**滑动窗口**

上面讲到了发送窗口。在 TCP 中，两端都维护着窗口：分别为发送端窗口和接收端窗口。

![](/files/-M_iWSt_-PYGMeBaYaZt)

发送端窗口是由接收窗口剩余大小决定的。接收方会把当前接收窗口的剩余大小写入应答报文，发送端收到应答后根据该值和当前网络拥塞情况设置发送窗口的大小，所以发送窗口的大小是不断变化的。

当发送端接收到应答报文后，会随之将窗口进行滑动。**滑动窗口实现了流量控制**。接收方通过报文告知发送方还可以发送多少数据，从而保证接收方能够来得及接收数据。

### 1.4 拥塞处理

拥塞处理和流量控制不同，后者是作用于接收方，保证接收方来得及接受数据。而前者是作用于网络，防止过多的数据拥塞网络，避免出现网络负载过大的情况。拥塞处理包括了四个算法，分别为：慢开始，拥塞避免，快速重传，快速恢复。

![](/files/-M_iWajJs3WlscGBT6kM)

#### **1.4.1 慢开始**

慢开始算法，顾名思义，就是在传输开始时将发送窗口从1开始指数级扩大，从而避免一开始就传输大量数据导致网络拥塞。慢开始算法步骤具体如下:

1. 连接初始设置拥塞窗口（Congestion Window） 为 1 MSS（一个分段的最大数据量）
2. 每过一个 RTT (往返时延) 就将窗口大小乘二
3. 指数级增长肯定不能没有限制的，所以有一个阈值限制，当窗口大小大于阈值时就会启动拥塞避免算法

#### **1.4.2 拥塞避免**

拥塞避免算法相比简单点，每过一个 RTT 窗口大小只加一，这样能够避免指数级增长导致网络拥塞，慢慢将大小调整到最佳值。在传输过程中可能定时器超时的情况，这时候 TCP 会认为网络拥塞了，会马上进行以下步骤：

* 将阈值设为当前拥塞窗口的一半
* 将拥塞窗口设为 1 MSS
* 启动拥塞避免算法

#### **1.4.3 快速重传/快恢复**

快速重传一般和快恢复一起出现。一旦接收端收到的报文出现失序的情况，接收端只会回复最后一个顺序正确的报文序号（没有 Sack 的情况下）。如果收到三个重复的 ACK，无需等待定时器超时再重发而是启动快速重传。具体算法分为两种：

* **RTT**(Round Trip Time) ：一个连接的往返时间，即数据发送时刻到接收到确认的时刻的差值。
* **RTO**(Retransmission Time Out) ：重传超时时间，即从数据发送时刻算起，超过这个时间便执行重传。

## 2. UDP

UDP（User Datagram Protocol），又叫用户数据报协议。UDP是一个无连接的、不可靠、基于数据报的传输协议。UDP只是报文（报文可以理解为一段段的数据）的搬运工，不会对报文进行任何拆分和拼装操作。

\*\*应用场景：\*\*当强调输出性能而非完整性时，如音频和多媒体的实时传输。有个视频流传输协议RTP的实时传输就是基于UDP封装而来的。

\*\*传输方式：\*\*支持一对一，一对多，多对多，多对一的方式，也就是说 UDP 提供了单播，多播，广播的功能。

**不可靠性：**

1. UDP是无连接的，不需要建立和断开链接。
2. UDP是不可靠的，它不会去备份数据，也不关心对方是否能收到数据。
3. UDP没有拥塞控制，一直以恒定的速度发送数据，即使网络条件不好，也不进行速率调整，因此可能导致丢包。

{% hint style="info" %}
如果你对内容有任何疑问，欢迎提交 [❕issues](https://github.com/MrEnvision/Front-end_learning_notes/issues) 或 [✉️ email](mailto:EnvisionShen@gmail.com)
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://sherwinshen.gitbook.io/qian-duan-xue-xi/network/tcp-udp.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
