【导语】
资深工程师老王手里的咖啡杯猛地一震,深褐色的咖啡在马克杯里划出惊慌的涟漪。"见鬼!"他拍桌而起,"这TCP分手姿势不对啊!"。老王盯着tcpdump瀑布流里乱序飘过的FIN标志,仿佛看到服务器和客户端在网线两端上演情感大戏——服务端的FIN包像被风吹走的情书,客户端的ACK像错过的末班车。一场由TCP协议主演的《谁先提分手》狗血剧,正在服务器与客户端间激情上演...
【事件回顾】
产品测试现场惊现灵异现象,只见Wireshark抓包文件赫然显示:连接关闭时经典的TCP挥手四部曲神奇地演变成了抓马五部曲:
服务端抓包:1️⃣ 服务端发FIN → 2️⃣ 收客户端FIN → 3️⃣ 服务端发ACK → 4️⃣ 服务端重传FIN → 5️⃣ 收客户端ACK
客户端抓包:1️⃣ 客户端发FIN → 2️⃣ 收服务端FIN → 3️⃣ 收服务端ACK → 4️⃣ 收服务端重传的FIN → 5️⃣ 客户端发ACK
在甜蜜的TCP连接建立后,客户端与服务端热络地开始了TLS握手。可惜,‘证书未知’的致命错误(Fatal级)如晴天霹雳,客户端急不可耐地甩出Alert消息宣告“我们结束了”(No.12)。随后双方毫不示弱,电光火石间争先挥起分手大旗——客户端发出FIN包(No.13),吼着“分”; 与此同时,服务端也抛出自家的FIN(No.14)与ACK包(No.15),喊着“分就分,谁怕谁”。尴尬的是,服务端的FIN包(No.14)与ACK包(No.15)像是忘记约定暗号的前任短信,客户端收到时一脸问号:‘说的啥玩意儿,发错了吧,完全看不懂,丢进垃圾桶’。等不来信息的服务端只好在重传计时器的滴答声中执拗地重发FIN(No.16),像是雨夜反复敲门的前任:‘开门啊,我只是想好好告别’。最终,当服务端的重传FIN包抵达时,客户端才懒洋洋回了个ACK(No.17),这场网络世界的分手闹剧才终于落下帷幕。
【时序梳理】
双方TLS握手过程中,在客户端侧发生了Fatal级别错误,客户端以TLS Alert消息告知服务端TLS握手已无法继续。于是服务端发出14号FIN包要求关闭连接,在FIN包未被送达前,客户端就傲娇宣言:"本宝宝等烦了!发出13号包FIN包主动关闭连接",在双方看来,各自都是主动发起连接关闭的,这导致:
1️⃣ 客户端随后收到服务端的14号FIN包,检查发现与自己预期的Ack(约定暗号)不符,只好嫌弃地扔掉,内心暗骂:"说的啥玩意儿"
2️⃣ 客户端收到服务器的15号ACK包,检查发现与自己预期的Seq(约定暗号)不符,表示看不懂,丢进垃圾桶,内心OS:“怎么老是收到垃圾信息”
3️⃣ 服务器发现自己的14号FIN包与15号ACK包都石沉大海,气得直跺脚:"居然不理我?看我重发!",于是重传16号FIN包,连带对齐了约定暗号Seq/Ack
4️⃣ 客户端收到16号FIN包,这才恍然大悟:"原来是要关闭连接呀"
5️⃣ 客户端发出17号ACK包,关闭与服务端的连接
连接关闭的交互流程图
由于都是主动关闭连接的,双方TCP连接都会转入TIME_WAIT状态,经确认确实如此,如下所示:
【后记】
TCP用严谨的四步舞曲试图框定离散的宿命,却总在时序的罅隙里上演错位的悲喜剧:FIN与ACK的追逐,恰似人类在代码中堆砌秩序高塔,而混沌总在挥手与重传间揭示终极真相——每次连接都埋着分离的基因,每次分离又孕育着重逢的密钥。当最后那个ACK消逝在数据洪流中,服务器风扇的嗡鸣并非终结的叹息,而是新连接的序曲。
【更多精彩内容】