网络协议相关

TCP与UDP

TCP

TCP:有连接的、稳定的、有序的、占用资源大的

TCP概述-可靠的、面向连接的、基于字节流、全双工的协议

image.png

TCP 是面向连接的协议

面向连接(connection-oriented):面向连接的协议要求正式发送数据之前需要通过「握手」建立一个逻辑连接,结束通信时也是通过有序的四次挥手来断开连接。

TCP 是面向连接的协议

三次握手

通过三次握手协商好双方后续通信的起始[序列号]、窗口缩放大小等信息 image.png

image.png

滑动窗口

滑动窗口(Sliding Window)是 TCP 协议中最为核心的机制之一

在面试中,如果提到 TCP,面试官紧接着很可能会问:“TCP 是如何保证可靠传输的?”或者“TCP 是如何进行流量控制的?”这时候,滑动窗口就是标准答案。

1. 为什么要使用滑动窗口?(解决性能问题)

在最原始的通信中,发送方发一个包,必须等接收方回一个 ACK(确认包),才能发下一个。

  • 缺点:往返时间(RTT)如果很长,网络吞吐量会极低。大部分时间都在“等待”,带宽被严重浪费。

滑动窗口的意义:允许发送方在收到 ACK 之前,连续发送多个数据包。这大大提高了网络吞吐率。


2. 滑动窗口是如何工作的?

你可以把它想象成一个在字节流上移动的“矩形框”。

发送方窗口 (Send Window)

发送方的数据可以分为四类:

  1. 已发送且已确认:窗口左侧的数据,已经安全送达。

  2. 已发送但未确认:窗口内的数据,已经发出去,但在等接收方的 ACK。

  3. 未发送但允许发送:窗口内的数据,接收方还有空间处理,可以立即发出去。

  4. 未发送且不允许发送:窗口右侧的数据,超出了接收方的处理能力。

“滑动”的过程:当窗口最左侧的包收到 ACK 时,窗口就会向右滑动,把原本“不允许发送”的数据纳入窗口中。

接收方窗口 (Receive Window)

接收方也有一个窗口,用来告诉发送方:“我现在的缓存区还有多大空间,你最多能再发多少字节过来。”这个空间大小通过 TCP 报文头部的 Window Size 字段传递。


3. 滑动窗口解决的两大核心问题

A. 流量控制 (Flow Control)

  • 原理:防止发送方发得太快,导致接收方缓冲区溢出。

  • 机制:接收方在 ACK 包中实时通告自己的 rwnd(接收窗口大小)。如果接收方处理不过来了,它就把窗口缩小,甚至设为 0。如果设为 0,发送方会停止发送,并定期发送“窗口探测”包。

B. 丢包重传 (可靠性)

滑动窗口配合 累计确认 (Cumulative ACK)快速重传 机制:

  • 如果中间丢了一个包(比如包 1, 2 到了,3 没到,4, 5 到了),接收方会一直回 ACK 3。

  • 发送方连续收到 3 个重复的 ACK 3,就知道包 3 丢了,立即重传,而不需要等待超时计时器到期。


4. 面试高级进阶:滑动窗口与拥塞窗口 (cwnd) 的区别

这是区分“中级”和“资深”开发者的关键点。

  • 流量控制 (rwnd):是为了保护接收方(别把我撑死)。

  • 拥塞控制 (cwnd):是为了保护网络(别把路堵死)。

  • 最终决定权:发送方实际能发送的数据量 = Min(rwnd, cwnd)。即取接收方窗口和拥塞窗口的最小值。

面向连接(connection-oriented):面向连接的协议要求正式发送数据之前需要通过「握手」建立一个逻辑连接,结束通信时也是通过有序的四次挥手来断开连接。

四次挥手

全双工连接的优雅关闭

TCP 连接是全双工的,这意味着客户端和服务器可以同时向对方发送数据。因此,关闭连接时,两个方向的连接必须单独关闭

1. 挥手流程全拆解

我们假设“客户端”发起主动关闭(其实服务端也可以发起主动关闭):

  • 第一次挥手(FIN):客户端发送一个 FIN 报文,告诉服务器:“我没数据要发给你了”。

    • 客户端状态:进入 FIN_WAIT_1
  • 第二次挥手(ACK):服务器收到后,回复一个 ACK。这表示:“收到,但我可能还有数据没发完,你等我一下”。

    • 服务端状态:进入 CLOSE_WAIT(这是一个关键状态,表示半关闭)。

    • 客户端状态:收到 ACK 后进入 FIN_WAIT_2。此时客户端不能发数据,但能收数据。

  • 第三次挥手(FIN):服务器数据发完了,发送自己的 FIN 报文给客户端,表示:“我这边也发完了,可以关了”。

    • 服务端状态:进入 LAST_ACK
  • 第四次挥手(ACK):客户端收到 FIN,回复最后一个 ACK,表示“知道了,拜拜”。

    • 客户端状态:进入 TIME_WAIT(核心考点),等待 2MSL 时间后关闭。

    • 服务端状态:收到确认后立即进入 CLOSED


二、 深度考点:为什么需要这些设计?

1. 为什么是 4 次而不是 3 次?

因为第二次和第三次挥手通常不能合并

  • 当服务端收到客户端的 FIN 时,它只能代表客户端不再发数据。

  • 但此时服务端可能还有数据正在处理中(比如一个大的下载任务或数据库查询),它需要先把剩余数据发完,才能发自己的 FIN

  • 如果强行合并成 3 次,会导致服务端还没发完的数据被强行中断。

2. 核心考点:TIME_WAIT 状态(2MSL)

这是面试官最喜欢问的地方。客户端发送完最后一个 ACK 后,为什么要等 2MSL(Maximum Segment Lifetime,报文最大生存时间)才关掉?

  1. 确保 ACK 到达:如果第四次的 ACK 丢了,服务端会认为自己的 FIN 没发成功,从而重发 FIN。如果客户端直接关了,服务端重发的 FIN 就得不到响应,无法正常关闭。

  2. 防止“陈旧报文”干扰:等待 2MSL 可以保证本次连接产生的所有数据包都从网络中消失。如果不等直接复用端口开启新连接,旧连接遗留的迟到数据包可能会被新连接误收,导致逻辑错误。


三、 面试高阶:异常状态分析

如果你能主动聊出这两个状态的问题,面试官会认为你具备实战经验:

1. 线上出现大量 CLOSE_WAIT 是怎么回事?

  • 原因:由于 CLOSE_WAIT 是服务端收到客户端 FIN 后进入的状态。如果服务端迟迟不进入第三次挥手(发 FIN),就会一直卡在这里。

  • 真相:通常是代码 Bug。程序逻辑里忘记调用 socket.close() 或者 connection.close(),导致连接没被正常关闭。在 iOS 开发中,如果你手动管理底层 Socket 却没能正确触发关闭逻辑,就会产生这个问题。

2. 出现大量 TIME_WAIT 是怎么回事?

  • 原因:这通常发生在高并发且使用短连接的服务端(比如某些网关转发服务)。

  • 风险:每个 TIME_WAIT 都会占用一个端口。如果积压过多,会导致新连接因为没有可用端口而失败。

  • 优化建议:在服务器内核层面开启 tcp_tw_reuse(重用)或使用长连接(Keep-Alive)来减少频繁的握手挥手。


四、 总结:挥手时的口语化理解

  • 客户端:我不发了 (FIN)。

  • 服务端:知道了,但我还没发完 (ACK) -> (过一会儿) -> 好了,我也发完了 (FIN)。

  • 客户端:收到,我挂了,再见 (ACK) -> (在门口等两分钟防止对面没听到) -> 彻底关闭


UDP

UDP(UserDatagramProtocol)是一个简单的面向消息的传输层协议,尽管UDP提供标头和有效负载的完整性验证(通过校验和),但它不保证向上层协议提供消息传递,并且UDP层在发送后不会保留UDP 消息的状态。因此,UDP有时被称为不可靠的数据报协议。

UDP是基于IP的简单协议,不可靠的协议。

UDP的优点:简单,轻量化。

UDP的缺点:没有流控制,没有应答确认机制,不能解决丢包、重发、错序问题。

UDP适用场景

UDP协议一般作为流媒体应用、语音交流、视频会议所使用的传输层协议,还有许多基于互联网的电话服务使用的VOIP(基于IP的语音)也是基于UDP运行的,实时视频和音频流协议旨在处理偶尔丢失的数据包,因此,如果重新传输丢失的数据包,则只会发生质量略有下降,而不是出现较大的延迟。

Http 与 Https

Socket 与WebSocket