面试官问你‘说说TCP三次握手’,你脱口而出SYN、SYN-ACK、ACK——但下一秒他追问:‘为什么不是两次?四次行不行?TIME_WAIT为什么是2MSL?’你卡住了。这很常见。协议栈不是背概念的地方,而是考你有没有真正‘跑过’网络数据。
三次握手,真只是为了‘建立连接’?
三次握手本质是同步双方初始序列号(ISN)+ 确认可达性。第一次SYN带客户端ISN;第二次SYN-ACK既要回SYN(带服务端ISN),又要ACK客户端ISN+1;第三次ACK确认服务端ISN+1。少一次,就有一方不知道对方能不能收、序列号对不对。比如两次握手后直接传数据,服务端发完SYN-ACK就挂了,客户端却以为连上了,数据全丢。
TIME_WAIT为什么非得等2MSL?
MSL(Maximum Segment Lifetime)是报文在网络里能活的最长时间,Linux默认60秒。2MSL≈120秒,不是拍脑袋定的。它干两件事:一是确保最后那个ACK真的被对方收到了——如果丢了,服务端重发FIN,客户端还能用TIME_WAIT状态响应;二是让网络中残留的旧连接报文自然消亡,避免它们误入新建立的同四元组连接里,造成数据混乱。你在压测时看到大量TIME_WAIT,别急着调小tcp_fin_timeout,先看是不是短连接滥用。
UDP真的无连接?那DNS查询为啥经常超时?
UDP不维护连接状态,但不代表没状态。DNS客户端发一个UDP查询包,53端口,等响应。如果500ms没回来,多数实现会重试(通常是1秒后重发,最多3次)。超时不是因为UDP‘不可靠’,而是因为:中间某个路由器ACL拦截了53/udp、防火墙丢包、或者递归DNS服务器太忙根本没处理。你可以用 tcpdump -i any port 53 抓包验证,而不是一上来就怀疑UDP设计缺陷。
HTTP/2和HTTP/3怎么绕开TCP队头阻塞?
HTTP/2在单个TCP连接上用二进制分帧+多路复用,但底层TCP还是按序交付——一个丢包,整条流卡住,所有请求跟着等重传。HTTP/3直接换掉TCP,用QUIC(基于UDP)。QUIC把‘连接’‘加密’‘流控’都自己实现,每个stream独立滑动窗口、独立重传。所以A流丢了一个包,只影响A流,B流照常跑。部署HTTP/3的前提不是换语言,而是CDN支持(Cloudflare、阿里云全站已开)、客户端系统版本(iOS 15+/Android 12+原生支持)。
写代码时,SO_REUSEADDR到底在重用啥?
它允许新进程绑定一个处于TIME_WAIT状态的本地端口。典型场景:服务重启。老进程退出后,它的socket还在TIME_WAIT,新进程若不加这个选项,bind()直接失败:‘Address already in use’。注意,SO_REUSEADDR不等于SO_REUSEPORT(后者允许多个进程监听同一端口,常用于多worker模型)。实际写服务时,Go的net.Listen默认带SO_REUSEADDR,但Python的socket.bind()需要手动设:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 8080))协议栈问题没有标准答案,只有上下文里的合理选择。面试不是考你背RFC,而是看你遇到丢包、延迟高、连接数上不去时,会不会一层层往下扒:是应用层逻辑阻塞?还是TCP窗口缩了?还是网卡中断合并在捣鬼?