文章目录
各位搞网络的小伙伴注意啦(敲黑板)!今天咱们要聊的这个TCP连接/断开机制,简直是面试必考题+实际调试中的"鬼见愁"。别看原理好像很简单,信不信我随便说几个场景就能让你怀疑人生?
一、连接建立:三次握手真不是凑数
先来段硬核现场还原!当你在浏览器输入www.example.com
时,背后发生了什么?
1.1 第一回合:SYN突袭
客户端(你的电脑)突然向服务器发送一个SYN=1的数据包,附带一个随机生成的初始序列号(比如Seq=10086)。这个动作就像突然拍别人肩膀:“嘿兄弟,能听见吗?”
关键点来了(画重点):
- 随机序列号是为了防止历史连接混淆
- 此时客户端进入
SYN_SENT
状态(等回复等到花儿都谢了)
1.2 第二回合:SYN-ACK组合拳
服务器收到SYN后,如果端口开着,就会回个SYN=1, ACK=1的包。这里有两个骚操作:
- 确认号填
客户端序列号+1
(Ack=10087) - 自己也生成随机序列号(比如Seq=2024)
这时候服务器进入SYN_RCVD
状态,内心OS:“小样儿,我回应了你可别装死啊!”
1.3 第三回合:终极确认
客户端收到SYN-ACK后,必须回一个ACK=1的包(Ack=2025)。到这里连接才正式建立,双方进入ESTABLISHED
状态。
灵魂拷问时刻:为什么非要三次?两次不行吗?
举个栗子🌰:假设网络延迟导致旧SYN包到达,服务器响应后如果只有两次握手,客户端根本不会理会这个"过期的约定",但服务器却傻等资源…三次握手完美解决这种"单相思"问题!
二、连接断开:四次挥手比分手还纠结
断开连接可比建立复杂多了(毕竟分手总是痛苦的)。假设现在客户端主动关闭:
2.1 第一挥:FIN温柔告别
客户端发送FIN=1的包(假设Seq=5000),进入FIN_WAIT_1
状态。这相当于说:“我要走了,但还能收你的消息哦~”
2.2 第二挥:ACK确认收到
服务器回个ACK=1(Ack=5001),进入CLOSE_WAIT
状态。此时客户端到服务器的通道关闭,但反向通道还开着!
这里有个大坑🕳️:CLOSE_WAIT状态堆积会导致端口占用,常见于服务器没正确调用close()的情况(多少人在这里栽过跟头?)
2.3 第三挥:服务器的FIN暴击
当服务器也准备好关闭时,发送自己的FIN=1包(假设Seq=8000),进入LAST_ACK
状态。这波操作相当于:“好的,我也要撤了!”
2.4 第四挥:最后的绅士礼仪
客户端回ACK=1(Ack=8001),进入TIME_WAIT
状态。注意这个状态要维持2MSL(最长报文寿命的两倍)!
为什么需要TIME_WAIT?三大理由:
- 确保最后一个ACK能到达
- 让旧连接的报文在网络中消失
- 防止出现"鬼影连接"(听过端口复用的灵异事件吗?)
三、实战踩坑记录
3.1 生产事故:TIME_WAIT过多怎么办?
某次大促时监控突然报警:80端口耗尽!查原因发现短连接产生大量TIME_WAIT。解决方案:
# 调整内核参数
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
(不过要注意NAT环境下的副作用!)
3.2 调试奇遇:半关闭状态
用Python写了个简易代理:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.shutdown(socket.SHUT_WR) # 只关写通道
结果对方服务器一直不返回数据…原来调用shutdown()会触发FIN发送,但recv()还能继续收数据!
四、高频灵魂拷问
Q:握手阶段能传数据吗?
A:第三次握手时可以携带数据(但SYN包本身不能带数据!)
Q:为什么服务端SYN和ACK要合并发送?
A:TCP协议允许将多个标志位同时置1,这是协议栈的优化操作
Q:出现大量SYN_RCVD状态怎么办?
A:八成是遭遇SYN Flood攻击了!赶紧上syncookies:
sysctl -w net.ipv4.tcp_syncookies=1
五、协议栈的隐藏关卡
你以为四次挥手就完了?在某些情况下:
- 同时关闭(双方同时发FIN)会进入
CLOSING
状态 - RST复位报文可以直接中断连接(相当于"滚!"的粗暴分手)
- Keep-Alive机制会让看似断开的连接"死灰复燃"
六、写在最后
看完这些是不是觉得TCP/IP设计者都是处女座?(笑)最后送大家一个调试锦囊:tcpdump抓包大法好! 下次遇到连接问题,直接:
tcpdump -i any tcp port 80 -nn -v
亲眼看看握手挥手的每个细节,比任何理论都管用!