资讯动态

TCP连接的一生

发布时间:2026/10/23 7:58:14 来源:尧图企业网站定制
TCP 连接的一生面试问三次握手背得出来为什么要三次语塞。更实际的案子在生产里端口被 TIME_WAIT 耗尽导致连不上两条消息粘在一个包里解析出鬼数据。这些全是连接「一生」里的设计决定的把生老病死走一遍每个「为什么」就有了着落。出生三次握手第一次客户端发 SYN我能发。第二次服务端回 SYN 加 ACK我收到了我也能发。第三次客户端回 ACK我知道你能收了。为什么不是两次。两次的话服务端无法确认自己的应答对方收到了更实际的隐患是历史连接网络里滞留的旧 SYN 迟到重传会让服务端为一个早该作废的连接分配资源。第三次握手就是让客户端有机会说「这是新连接旧的别认」。服务端客户端服务端客户端SYN我能发SYN ACK我收到我也能发ACK确认可以开聊死亡四次挥手第一次客户端发 FIN我说完了。第二次服务端回 ACK收到我可能还有话说。第三次服务端发 FIN我也说完了。第四次客户端回 ACK那散伙。为什么中间是两次挥手是四次。因为 TCP 是半关闭的一方说完了另一方还能继续发ACK 和 FIN 各管各的不能合并。TIME_WAIT最后的体面客户端回完最后那个 ACK不立刻走进入 TIME_WAIT等待两倍的报文最大生存时间。等这两下有两个作用保证最后的 ACK 丢了还能重传让这个连接的旧报文在网络里消散别跑到下一个同端口的新连接里捣乱。代价是在高并发场景下大量短连接的主动关闭方会堆积 TIME_WAIT端口耗尽后新连接直接失败。排查时用ss -s或netstat -an | grep TIME_WAIT | wc -l数一数方向就清楚了。解法是长连接复用、连接池、以及调内核参数各有适用场景。粘包字节流没有消息边界TCP 送的是字节流「消息」这个概念它不认识。你 send 两次对方可能一次 recv 全收到粘包也可能收到半截拆包。缓冲区、MSS、Nagle 算法都在其中起作用但根源就一条边界得自己定。三种定法。长度前缀消息头带长度读满才算一条最常用。分隔符换行或者特殊字符结尾文本协议爱用。定长每条一样长简单但浪费。亲手看粘包下面用一段 Python 起个本地 socket 服务客户端分两次发消息看边界怎么丢再对照长度前缀的帧格式。直接运行python sticky_packet.py。# 运行python sticky_packet.py# TCP 粘包演示两次 send服务端一次 recv 边界就丢了。importsocketimportstructimportthreadingimporttimedefserver(sock):conn,_sock.accept()databwhileTrue:chunkconn.recv(1024)# 字节流不认消息来多少读多少ifnotchunk:breakdatachunkiflen(data)8:breakprint(服务端一次 recv 读到,data.decode(),hello 和 world 粘在了一起)conn.sendall(bok)conn.close()defmain():srvsocket.socket()srv.bind((127.0.0.1,0))srv.listen()portsrv.getsockname()[1]tthreading.Thread(targetserver,args(srv,))t.start()csocket.create_connection((127.0.0.1,port))c.sendall(bhello)# 消息一time.sleep(0.05)c.sendall(bworld)# 消息二print(客户端分两次 sendhello / world)c.recv(16)c.close()t.join()print()print(长度前缀的帧格式[4 字节长度][消息体])framestruct.pack(I,5)bhello(n,)struct.unpack(I,frame[:4])print(示意帧头,n,字节消息体 ,frame[4:].decode())print(读满 n 字节才算一条消息粘包和拆包都被挡在帧外。)if__name____main__:main()记住一句话TCP 只送字节流消息边界要自己定。适用边界这一篇管连接生命周期和消息边界。拥塞控制慢启动、拥塞避免、滑动窗口、以及 TLS 握手叠在 TCP 之上的那套是另外的题。TIME_WAIT 的内核参数调整有副作用动手前先确认自己的场景是短连接风暴还是长连接缺失。跑完演示回头看自己的协议解析代码「读满一条」的判断在不在不在的地方就是下一个粘包事故。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑