网络协议相关
TCP与UDP
TCP
TCP:有连接的、稳定的、有序的、占用资源大的
TCP概述-可靠的、面向连接的、基于字节流、全双工的协议

TCP 是面向连接的协议
面向连接(connection-oriented):面向连接的协议要求正式发送数据之前需要通过「握手」建立一个逻辑连接,结束通信时也是通过有序的四次挥手来断开连接。
TCP 是面向连接的协议
三次握手
通过三次握手协商好双方后续通信的起始[序列号]、窗口缩放大小等信息


滑动窗口
滑动窗口(Sliding Window)是 TCP 协议中最为核心的机制之一。
在面试中,如果提到 TCP,面试官紧接着很可能会问:“TCP 是如何保证可靠传输的?”或者“TCP 是如何进行流量控制的?”这时候,滑动窗口就是标准答案。
1. 为什么要使用滑动窗口?(解决性能问题)
在最原始的通信中,发送方发一个包,必须等接收方回一个 ACK(确认包),才能发下一个。
- 缺点:往返时间(RTT)如果很长,网络吞吐量会极低。大部分时间都在“等待”,带宽被严重浪费。
滑动窗口的意义:允许发送方在收到 ACK 之前,连续发送多个数据包。这大大提高了网络吞吐率。
2. 滑动窗口是如何工作的?
你可以把它想象成一个在字节流上移动的“矩形框”。
发送方窗口 (Send Window)
发送方的数据可以分为四类:
-
已发送且已确认:窗口左侧的数据,已经安全送达。
-
已发送但未确认:窗口内的数据,已经发出去,但在等接收方的 ACK。
-
未发送但允许发送:窗口内的数据,接收方还有空间处理,可以立即发出去。
-
未发送且不允许发送:窗口右侧的数据,超出了接收方的处理能力。
“滑动”的过程:当窗口最左侧的包收到 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,报文最大生存时间)才关掉?
-
确保 ACK 到达:如果第四次的
ACK丢了,服务端会认为自己的FIN没发成功,从而重发FIN。如果客户端直接关了,服务端重发的FIN就得不到响应,无法正常关闭。 -
防止“陈旧报文”干扰:等待 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运行的,实时视频和音频流协议旨在处理偶尔丢失的数据包,因此,如果重新传输丢失的数据包,则只会发生质量略有下降,而不是出现较大的延迟。