计算机网络八股文——TCP,UDP

发布于:2025-08-01 ⋅ 阅读:(18) ⋅ 点赞:(0)

计算机网络八股文——TCP,UDP

TCP、UDP 的区别

TCP (Transmission Control Protocol)UDP (User Datagram Protocol) 是互联网协议栈传输层的两个核心协议。它们的主要区别如下:

  • 连接性:
    • TCP:面向连接的协议。在数据传输前,客户端和服务器之间必须先建立连接(三次握手),数据传输完成后需要释放连接(四次挥手)。
    • UDP:无连接的协议。它在发送数据前不需要建立连接,直接将数据作为数据报发送。
  • 可靠性:
    • TCP: 提供可靠的数据传输。它保证数据按序到达、不丢失、不重复且无错误。这通过序列号、确认应答 (ACK)、重传机制和流量控制等实现。
    • UDP:不可靠的。它不保证数据一定会到达目的地,也不保证数据的顺序或完整性。它只负责发送数据,任何可靠性问题需要应用层处理。
  • 顺序性:
    • TCP: 保证有序交付。数据段被编号,接收方会根据编号将数据重新排序。
    • UDP: 不保证顺序。数据报可能乱序到达,如果需要顺序性,应用层需要自行处理。
  • 错误检测与恢复:
    • TCP: 提供全面的错误检测和恢复机制。如果数据段丢失或损坏,TCP 会检测到并进行重传。
    • UDP: 仅提供基本的校验和用于错误检测,但不提供错误恢复功能。
  • 流量控制:
    • TCP: 实现流量控制,防止发送方发送数据过快导致接收方来不及处理。它使用滑动窗口机制。
    • UDP: 没有内置的流量控制
  • 拥塞控制:
    • TCP: 包含复杂的拥塞控制机制,以避免网络拥塞。它根据网络状况调整发送速率。
    • UDP: 没有拥塞控制。它会以请求的速度发送数据,可能会加剧网络拥塞。
  • 报头大小:
    • TCP: 报头较大(通常为 20-60 字节),因为包含可靠性和控制机制的开销。
    • UDP: 报头较小(8 字节),因为它更简单,功能更少。
  • 速度:
    • TCP: 由于建立连接、可靠性机制和拥塞控制的开销,通常较慢
    • UDP: 由于简单、无连接和缺乏控制机制,通常较快

TCP、UDP 的优缺点

TCP 的优点:

  • 可靠性高: 保证数据不丢失、不重复、按序到达。
  • 有序性: 保证数据传输的顺序。
  • 流量控制: 防止发送方发送过快导致接收方缓冲区溢出。
  • 拥塞控制: 避免网络拥塞,提高网络利用率。

TCP 的缺点:

  • 传输速度慢: 由于各种控制机制(如建立连接、确认、重传等)的开销,传输效率相对较低。
  • 资源消耗大: 需要维护连接状态、缓冲区等,对系统资源消耗较大。
  • 实时性差: 由于重传和拥塞控制机制,不适合对实时性要求高的应用。

UDP 的优点:

  • 传输速度快: 无需建立连接,没有复杂的控制机制,开销小,传输效率高。
  • 资源消耗小: 不需要维护连接状态,对系统资源消耗少。
  • 实时性好: 适用于对实时性要求高的应用,如音视频传输。
  • 支持一对多、多对多通信: 支持广播和多播。

UDP 的缺点:

  • 不可靠: 不保证数据传输的可靠性,可能出现丢包、乱序、重复。
  • 无序性: 不保证数据报的到达顺序。
  • 无流量控制和拥塞控制: 可能导致网络拥塞或接收方溢出。

TCP UDP 适用场景

TCP 的适用场景:

  • 对数据可靠性要求高的应用: 比如文件传输 (FTP)、网页浏览 (HTTP/HTTPS)、电子邮件 (SMTP/POP3/IMAP)。
  • 需要准确传输数据的应用: 数据库同步、远程登录 (SSH)。
  • 对传输速度要求不高但对数据完整性要求严苛的应用。

UDP 的适用场景:

  • 对实时性要求高,允许少量丢包的应用: 比如在线视频、音频直播、网络电话 (VoIP)、在线游戏。
  • 需要高效传输、开销小的应用: DNS (域名解析)、SNMP (简单网络管理协议)。
  • 需要广播或多播的应用。

TCP 为什么是可靠连接

TCP 之所以是可靠连接,因为它在数据传输过程中采取了多种机制来确保数据的完整性、顺序性和不重复性:

  1. 序列号 (Sequence Numbers) 和确认应答 (Acknowledgments, ACK):
    • 发送方给每个发送的数据报文段分配一个序列号
    • 接收方收到数据后,会发送一个确认应答 (ACK),其中包含它期望接收的下一个数据段的序列号。
    • 如果发送方在一定时间内没有收到某个数据段的 ACK,就会认为该数据段丢失,并进行重传
  2. 超时重传机制:
    • 发送方在发送数据后会启动一个计时器。如果在计时器超时之前没有收到相应的 ACK,发送方就会认为数据丢失,并重新发送该数据。
  3. 流量控制 (Flow Control):
    • TCP 使用滑动窗口协议实现流量控制。接收方会告知发送方自己还有多少可用的缓冲区空间,发送方根据这个信息来调整发送速率,防止发送过快导致接收方缓冲区溢出。
  4. 拥塞控制 (Congestion Control):
    • TCP 会根据网络拥塞状况动态调整发送数据的速率。它通过慢启动、拥塞避免、快速重传和快速恢复等算法来避免网络过载。
  5. 校验和 (Checksum):
    • TCP 对报文头部和数据部分都进行校验和计算,用于检测数据在传输过程中是否发生了错误。如果校验和不匹配,报文段将被丢弃。
  6. 连接管理:
    • 通过三次握手建立连接,确保双方都准备好进行数据传输。
    • 通过四次挥手终止连接,确保所有数据都已传输完毕并正确关闭连接。

典型网络模型,简单说说有哪些

典型的网络模型主要有两种:

  1. OSI 模型 (Open Systems Interconnection Model)
    • OSI 模型是一个理论上的概念模型,由国际标准化组织 (ISO) 发布,旨在为各种计算机和网络设备在不同的网络中进行通信提供一个标准框架。它将网络通信过程分为七个层次
      1. 物理层 (Physical Layer): 负责传输比特流,定义物理接口和传输介质。
      2. 数据链路层 (Data Link Layer): 负责在物理链路上提供可靠的数据传输,处理帧的传输、错误检测和流量控制。
      3. 网络层 (Network Layer): 负责数据包的路由和寻址,实现不同网络之间的互联。
      4. 传输层 (Transport Layer): 提供端到端的可靠或不可靠数据传输服务(如 TCP 和 UDP)。
      5. 会话层 (Session Layer): 管理和协调不同应用程序之间的会话,建立、管理和终止会话。
      6. 表示层 (Presentation Layer): 处理数据的表示形式,如数据加密、解密、压缩和格式转换。
      7. 应用层 (Application Layer): 直接为用户提供服务,例如 HTTP、FTP、SMTP 等应用协议。
  2. TCP/IP 模型 (Transmission Control Protocol/Internet Protocol Model)
    • TCP/IP 模型是实际应用中更为流行和广泛使用的模型,是互联网的基础。它将网络通信过程分为四个层次
      1. 网络接口层 (Network Access Layer): 对应 OSI 模型的物理层和数据链路层,处理物理传输和数据链路上的数据传输。
      2. 互联网层 (Internet Layer): 对应 OSI 模型的网络层,负责 IP 数据包的路由和寻址。
      3. 传输层 (Transport Layer): 对应 OSI 模型的传输层,提供端到端的通信服务(如 TCP 和 UDP)。
      4. 应用层 (Application Layer): 对应 OSI 模型的会话层、表示层和应用层,为用户提供具体的应用服务。

总结区别:

  • OSI 模型是理论模型,TCP/IP 模型是事实标准。
  • OSI 模型有七层,TCP/IP 模型有四层。 TCP/IP 模型将 OSI 的物理层和数据链路层合并为网络接口层,将会话层、表示层和应用层合并为应用层。

Http1.1 和 Http1.0 的区别

HTTP/1.1 是对 HTTP/1.0 的重大改进,主要区别如下:

  1. 持久连接 (Persistent Connections):
    • HTTP/1.0: 默认情况下,每个请求/响应对都会建立和关闭一个 TCP 连接。这意味着每请求一个资源,客户端都需要重新建立一次 TCP 连接,效率较低。
    • HTTP/1.1: 默认支持持久连接 (Keep-Alive)。一个 TCP 连接在发送完一个请求/响应后可以保持打开状态,用于发送后续的请求。这大大减少了 TCP 连接建立和关闭的开销,提高了性能。
  2. 管道化 (Pipelining):
    • HTTP/1.0: 不支持管道化。客户端必须等到上一个请求的响应收到后才能发送下一个请求。
    • HTTP/1.1: 支持管道化。客户端可以在收到前一个响应之前,连续发送多个请求。服务器也会按照请求的顺序依次发送响应。这进一步提高了性能,但有“队头阻塞”问题。
  3. 缓存处理 (Caching):
    • HTTP/1.0: 缓存机制较简单,主要通过 Expires 头部字段控制。
    • HTTP/1.1: 引入了更强大的缓存控制机制,例如 Cache-ControlETag (实体标签) 和 If-None-Match 等,允许更精细地控制缓存行为,减少不必要的请求。
  4. 带宽优化及内容协商 (Bandwidth Optimization & Content Negotiation):
    • HTTP/1.0: 不支持断点续传和范围请求。
    • HTTP/1.1: 引入了范围请求 (Range Requests),允许客户端只请求资源的某个部分,支持断点续传。同时支持内容协商,允许客户端和服务器商定最适合传输的资源版本(如语言、编码、媒体类型等),通过 AcceptAccept-LanguageAccept-Encoding 等头部字段实现。
  5. 主机头域 (Host Header Field):
    • HTTP/1.0: 没有强制要求包含 Host 头部字段。这使得在同一 IP 地址上托管多个域名变得困难(因为服务器无法区分请求是发给哪个域名的)。
    • HTTP/1.1: 强制要求客户端在请求中包含 Host 头部字段。这使得一台服务器可以识别并处理发往不同域名(虚拟主机)的请求。
  6. 错误状态码:
    • HTTP/1.0: 状态码定义较少。
    • HTTP/1.1: 引入了更多的状态码,例如 409 Conflict (冲突)、410 Gone (已失效) 等,提供了更详细的错误信息。

URI(统一资源标识符)和URL(统一资源定位符)之间的区别

  • URI (Uniform Resource Identifier - 统一资源标识符):
    • 定义: URI 是一个用于标识互联网上任何资源的字符串。它提供了一种在互联网上唯一标识资源的通用方法。
    • 范围: URI 的范围更广,它不仅可以标识可以被“定位”的资源(如网页),还可以标识抽象的或不可直接定位的资源(如一个人的名字、一个 ISBN 书号等)。
    • 组成: URI 可以是 URL 或 URN。
      • URL (统一资源定位符): 描述如何“定位”资源。
      • URN (统一资源名称): 描述资源的“名称”,但不描述如何定位。例如 urn:isbn:0451450523 标识一本书,但没有说明如何找到这本书。
    • 作用: 仅仅是标识,不一定提供访问资源的方法。
  • URL (Uniform Resource Locator - 统一资源定位符):
    • 定义: URL 是 URI 的一个子集,它不仅标识了资源,还提供了定位该资源的方法(即如何访问该资源)。
    • 作用: 指明资源的具体位置和访问方式,通常包括协议、主机名、端口号、路径等信息。
    • 例子:
      • https://www.example.com/index.html 这是一个 URL,它标识了一个网页资源,并指明了通过 HTTPS 协议在 www.example.com 主机的根路径下访问 index.html 文件。
      • ftp://ftp.example.com/pub/file.zip 这是一个 URL,它标识了一个文件,并指明通过 FTP 协议访问。

简单来说:

  • 所有的 URL 都是 URI。
  • 但不是所有的 URI 都是 URL。 URI 还可以是 URN。

你可以把 URI 理解为资源的“身份证号”,而 URL 则是资源的“家庭住址”。身份证号是唯一标识,而家庭住址告诉你如何找到这个人。

三次握手、四次挥手

什么是三次握手 ⭐⭐⭐⭐⭐

三次握手是 TCP 连接建立的过程,目的是为了在客户端和服务器之间建立可靠的连接。这个过程确保了双方都能够发送和接收数据。

三次握手过程如下:

  1. 第一次握手 (SYN):客户端 -> 服务器
    • 客户端向服务器发送一个 SYN (Synchronize Sequence Number) 报文段,请求建立连接。
    • 报文段中包含客户端的初始序列号 (ISN_client)。
    • 此时,客户端进入 SYN_SENT 状态。
  2. 第二次握手 (SYN + ACK):服务器 -> 客户端
    • 服务器收到客户端的 SYN 报文后,如果同意建立连接,它会向客户端发送一个 SYN + ACK 报文段。
    • 这个报文段包含服务器的初始序列号 (ISN_server)。
    • 同时,它会发送一个确认应答 (ACK),其值是客户端的 ISN_client + 1,表示已收到客户端的 SYN 请求。
    • 此时,服务器进入 SYN_RCVD 状态。
  3. 第三次握手 (ACK):客户端 -> 服务器
    • 客户端收到服务器的 SYN + ACK 报文后,会向服务器发送一个 ACK 报文段。
    • 这个报文段的确认应答值是服务器的 ISN_server + 1,表示已收到服务器的 SYN。
    • 客户端进入 ESTABLISHED 状态。
    • 服务器收到客户端的 ACK 报文后,也进入 ESTABLISHED 状态。

至此,TCP 连接建立成功,客户端和服务器可以开始互相发送数据

为什么三次握手中客户端还要发送一次确认呢?可以二次握手吗?⭐⭐⭐⭐

为什么客户端还要发送一次确认(第三次握手)?

第三次握手的目的是为了防止已失效的连接请求报文段突然又传到服务器,导致服务器错误地建立连接,浪费资源。

考虑以下场景:

假设客户端A发送了一个连接请求(SYN报文),由于网络延迟,这个请求在网络中滞留了很长时间。客户端A没有收到服务器的响应,于是它重新发送了一个连接请求,并成功地与服务器B建立了连接,然后数据传输完毕并关闭了连接。

过了一段时间,之前滞留的那个旧的SYN报文段终于到达了服务器B。服务器B收到这个旧的SYN后,会认为客户端A又要发起新的连接,于是会发送 SYN+ACK 报文给客户端A。

如果没有第三次握手,服务器B就会认为连接已经建立成功,并进入 ESTABLISHED 状态,等待客户端A发送数据。但实际上,客户端A根本没有发送新的连接请求,它也不会理会这个旧的 SYN+ACK 报文,更不会发送数据。

这样一来,服务器B就会白白地维护一个根本不存在的连接,浪费了服务器资源。

有了第三次握手,客户端A收到服务器B发送的旧的 SYN+ACK 报文时,由于它并没有发起这个连接请求,所以它会忽略这个报文,不会发送第三次 ACK。服务器B在等待一段时间后收不到客户端A的 ACK,就会超时并关闭这个半开的连接,从而避免了资源的浪费。

可以二次握手吗?

不可以。 二次握手无法解决上述“已失效的连接请求报文段”的问题。

如果只有两次握手:

  1. 客户端发送 SYN。
  2. 服务器收到 SYN 后,发送 SYN+ACK,并认为连接已建立。

同样以上述场景为例:客户端A的旧SYN报文到达服务器B,服务器B发送 SYN+ACK,并认为连接已建立。此时,服务器B仍然会白白地维护一个不存在的连接。客户端A由于没有发起请求,自然也不会发送数据,服务器B会一直等待,造成资源浪费。

所以,三次握手是建立可靠连接所必需的最小次数,它确保了客户端和服务器都确认了对方的发送和接收能力,并能够识别并拒绝旧的连接请求。

为什么服务端易受到 SYN 攻击?

SYN 攻击(SYN Flood Attack)是一种典型的拒绝服务 (DoS) 攻击。服务端容易受到 SYN 攻击的原因在于 TCP 三次握手的工作机制以及服务器维护连接状态的资源消耗。

攻击原理:

  1. 攻击者发送大量的 SYN 报文: 攻击者向目标服务器发送大量的 TCP 连接请求 (SYN 报文)。
  2. 服务器回应 SYN+ACK 并等待: 服务器收到每个 SYN 报文后,都会回应一个 SYN+ACK 报文,并进入 SYN_RCVD 状态,等待客户端的第三次 ACK 确认。
  3. 攻击者不发送第三次 ACK 或使用伪造源 IP: 攻击者要么不发送第三次 ACK 报文,要么使用伪造的源 IP 地址来发送 SYN 报文。
  4. 半开连接队列溢出: 服务器在 SYN_RCVD 状态下会为每个半开连接分配资源(如内存、端口等),并维护在一个半开连接队列中。由于攻击者不发送 ACK,这些半开连接会一直占用服务器资源,直到超时。当大量的 SYN 报文涌入时,半开连接队列很快就会被耗尽或溢出。
  5. 拒绝服务: 一旦半开连接队列被占满,新的合法连接请求将无法被处理,导致服务器无法为正常用户提供服务,从而造成拒绝服务。

服务端易受攻击的原因:

  • 资源消耗: 服务器在收到 SYN 后,需要分配一定的内存和 CPU 资源来维护这个半开连接的状态,并发送 SYN+ACK。这些资源在第三次 ACK 收到之前是不会释放的。
  • 状态维护: 服务器需要维护大量的“半开连接”状态,这些状态信息占用服务器的内存资源。
  • 超时机制: 服务器等待客户端第三次 ACK 的超时时间通常较长(几十秒到几分钟),这给了攻击者足够的时间来填满队列。
  • 非对称性: 攻击者发送一个 SYN 报文的开销很小,但服务器为了回应这个 SYN 报文,却需要进行状态维护和发送 SYN+ACK,开销相对较大。攻击者利用这种不对称性来消耗服务器资源。

防御措施(部分):

  • SYN Cookies: 一种在收到 SYN 时不立即分配资源,而是将连接信息编码到序列号中发送 SYN+ACK 的技术。只有当收到合法的 ACK 时才分配资源。
  • 加大半开连接队列容量: 增加服务器能够同时处理的半开连接数量,但这不是根本解决方案。
  • 缩短超时时间: 减少服务器等待第三次 ACK 的超时时间。
  • 防火墙和入侵检测系统: 识别和过滤 SYN 攻击流量。

什么是四次挥手

四次挥手是 TCP 连接终止(断开)的过程,目的是确保双方都已经完成了数据传输,并释放连接资源。由于 TCP 是全双工的,数据传输是双向的,因此断开连接需要四个步骤来确保两端的数据都已发送完毕。

四次挥手过程如下:

  1. 第一次挥手 (FIN):客户端 -> 服务器
    • 当客户端没有数据要发送时,它向服务器发送一个 FIN (Finish) 报文段,表示它已经没有数据要发送了,但仍然可以接收数据。
    • 客户端进入 FIN_WAIT_1 状态。
  2. 第二次挥手 (ACK):服务器 -> 客户端
    • 服务器收到客户端的 FIN 报文后,会立即发送一个 ACK 报文段作为确认。确认号为客户端的 FIN 报文序列号 + 1。
    • 此时,服务器进入 CLOSE_WAIT 状态。这表示服务器已经收到了客户端的关闭请求,但服务器可能还有数据要发送给客户端。在这个阶段,TCP 连接处于“半关闭”状态,客户端不再发送数据,但仍能接收数据;服务器仍能发送数据。
  3. 第三次挥手 (FIN):服务器 -> 客户端
    • 当服务器也发送完所有数据后,它会向客户端发送一个 FIN 报文段,表示它也没有数据要发送了,可以关闭连接了。
    • 服务器进入 LAST_ACK 状态。
  4. 第四次挥手 (ACK):客户端 -> 服务器
    • 客户端收到服务器的 FIN 报文后,会发送一个 ACK 报文段作为确认。确认号为服务器的 FIN 报文序列号 + 1。
    • 客户端进入 TIME_WAIT 状态。
    • 服务器收到客户端的 ACK 报文后,进入 CLOSED 状态,彻底关闭连接。
    • 客户端在 TIME_WAIT 状态会等待 2MSL (Maximum Segment Lifetime) 时间后才进入 CLOSED 状态。

为什么客户端最后还要等待 2MSL?

客户端在四次挥手最后发送完第四次 ACK 报文后,并不会立即进入 CLOSED 状态,而是会进入 TIME_WAIT 状态,并等待 2MSL (Maximum Segment Lifetime) 时间。MSL 是指一个数据报在网络中的最大生存时间。

等待 2MSL 的主要原因有以下两点:

  1. 确保最后一个 ACK 报文能够到达服务器:
    • 第四次挥手时,客户端发送的 ACK 报文有可能会在网络中丢失。
    • 如果客户端立即关闭连接(进入 CLOSED 状态),而这个 ACK 报文丢失了,那么服务器将永远收不到 ACK。
    • 服务器在 LAST_ACK 状态会因为没有收到 ACK 而超时并重传第三次挥手的 FIN 报文。
    • 如果客户端立即关闭了,就无法响应服务器的重传 FIN 报文,服务器会一直处于 LAST_ACK 状态,无法正常关闭。
    • 等待 2MSL 的时间:
      • 第一个 MSL:确保客户端发出的最后一个 ACK 报文能够到达服务器。
      • 第二个 MSL:如果服务器的 FIN 报文因客户端的 ACK 丢失而重传,确保这个重传的 FIN 报文能够到达客户端。
    • 这样,即使最后一个 ACK 丢失,服务器重传 FIN,客户端也能再次收到,并再次发送 ACK,从而保证服务器能够正常关闭。
  2. 防止“旧连接”的数据报在网络中出现,干扰“新连接”:
    • 想象一下,如果客户端在关闭连接后立即重新建立一个相同端口号的新连接。
    • 如果旧连接中还有一些迟到的数据报文段(可能是由于网络延迟),它们可能会在新的连接建立后才到达。
    • 这些迟到的旧报文段可能会被新连接误认为是有效数据,导致数据混乱。
    • 等待 2MSL 可以确保在当前连接中发送的所有数据报文段都在网络中消逝,避免它们在新的连接建立后干扰新的连接。因为 2MSL 是一个数据报文在网络中的最大往返时间。

总结: 2MSL 的等待是为了可靠地终止 TCP 连接,并避免旧连接的残留数据对新连接造成干扰

为什么建立连接是三次握手,关闭连接确是四次挥手呢?

建立连接是三次握手,而关闭连接是四次挥手,主要原因在于 TCP 的全双工 (Full-Duplex) 特性。

建立连接是三次握手:

  • 在建立连接时,客户端发送 SYN 表示“我准备好发送数据了”,服务器收到后,发送 SYN+ACK 表示“我收到你的请求,我也准备好发送数据了,并且确认你的 SYN”,客户端再发送 ACK 表示“我收到你的确认了”。
  • 这个过程中,服务器的 SYN 和 ACK 是可以合并在同一个报文段中发送的,因为服务器在收到客户端的 SYN 后,如果同意连接,它可以立即发送自己的 SYN 和对客户端 SYN 的确认。这样,就只需要三次握手,双方就都确认了彼此的发送和接收能力。

关闭连接是四次挥手:

  • TCP 是全双工的,意味着客户端和服务器可以同时发送和接收数据。

  • 当一方(比如客户端)要关闭连接时,它发送 FIN 报文表示“我这边没有数据要发送了,请求关闭我这一方向的连接”。

  • 但此时,服务器可能还有数据要发送给客户端。因此,服务器会先发送一个 ACK 报文确认收到客户端的 FIN,但并不会立即发送自己的 FIN。

  • 服务器进入 CLOSE_WAIT 状态后,会继续发送它剩余的数据给客户端。只有当服务器也发送完所有数据后,它才会发送自己的 FIN 报文给客户端,表示“我这边也发送完了,可以关闭我这一方向的连接了”。

  • 客户端收到服务器的 FIN 后,再发送 ACK 确认。

  • 因此,关闭连接的过程是两个独立的半连接关闭过程:

    1. 客户端关闭其发送数据通路,服务器确认。

    2. 服务器关闭其发送数据通路,客户端确认。

      这两个关闭请求和确认不能像三次握手那样合并,因为服务器可能在收到客户端的 FIN 后,仍然有数据需要发送。所以,服务器的 ACK 和 FIN 往往是分开发送的。

总结来说:

  • 三次握手时,服务器在收到客户端的 SYN 后,可以立即回应自己的 SYN 和对客户端 SYN 的 ACK,因为此时客户端和服务器都是请求开始通信,没有数据流向的阻塞问题。
  • 四次挥手时,客户端发送 FIN 后,表示它不再发送数据了,但服务器可能还有数据要发送。服务器不能立即发送 FIN,而必须等到它所有数据发送完毕后才能发送 FIN。因此,服务器的 ACK 和 FIN 是独立的两个动作,不能合并,从而导致了四次挥手。

网站公告

今日签到

点亮在社区的每一天
去签到