🌟 一、TCP核心特性:可靠传输的秘密
1️⃣ 有连接 & 全双工
- 双向通道:建立连接后,客户端↔服务器可同时收发数据
- 可靠传输三板斧:
- 确认应答(ACK)
- 接收方返回
ACK=接收序号+数据长度
- 示例:发送
SEQ=100
(数据长度50) → 收到ACK=150
- 接收方返回
- 超时重传
- 未收到ACK则自动重发,解决丢包问题
- 数据排序
- 通过SEQ序号重组乱序数据包
- 确认应答(ACK)
2️⃣ 字节流传输
- 无边界限制:像水管流水,数据可任意拆分/合并
- 粘包问题:需应用层自行处理消息边界(如添加长度头)
🔑 二、三次握手:连接建立的精妙设计
TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而建立连接是通过三次握手来进行的。
1️⃣ 握手流程
2️⃣ 三大核心作用
- 验证链路畅通(投石问路)
- 能力验证:
握手阶段 验证的能力 第一次 客户端能发送 第二次 服务器能收 + 客户端能收 第三次 服务器能发 - 参数协商:确定起始序号(防数据混淆)
- 从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可以携带数据的,这也是面试常问的题。
3️⃣ 为什么能合并ACK和SYN?
- 操作系统内核自动处理,时机相同 → 合并为一次报文
一旦完成三次握手,双方都处于 ESTABLISHED 状态,此时连接就已建立完成,客户端和服务端就可以相互发送数据了。
💣 三、四次挥手:断开连接的江湖恩怨
1️⃣ 挥手流程
2️⃣ 关键差异
- ACK由内核自动发,FIN由代码触发
- 对于四次挥手来说,ACK是内核控制的,但是FIN的触发,通过应用程序(也就是java写的程序),调用close,假如说没写close,就甚至不发送
- 四次挥手为何不能合并?
- 服务器收到FIN后,可能还有数据要发送 → 先ACK,发完数据再FIN:
- 服务器收到客户端的 FIN 报文时,内核会马上回一个 ACK 应答报文,但是服务端应用程序可能还有数据要发送,所以并不能马上发送 FIN 报文,而是将发送 FIN 报文的控制权交给服务端应用程序:如果服务端应用程序有数据要发送的话,就发完数据后,才调用关闭连接的函数;如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数,所以说,是否要发送第三次挥手的控制权不在内核,而是在被动关闭方的应用程序,因为应用程序可能还有数据要发送,由应用程序决定什么时候调用关闭连接的函数,当调用了关闭连接的函数,内核就会发送 FIN 报文了,所以服务端的 ACK 和 FIN 一般都会分开发送。
- 对于三次握手来说,中间的两次ACK和SYN,都是在内核中,操作系统负责进行 时机都是在收到SYN之后,此时同一时机,就可以合并
⚠️ 四、必懂TCP状态:程序员排错指南
状态 | 触发场景 | 排查重点 |
---|---|---|
LISTEN | 服务器绑定端口后 | 检查端口占用/防火墙 |
ESTABLISHED | 连接建立成功 | 正常通信状态 |
CLOSE_WAIT | 被动方收到FIN后 | 🔥 代码没写socket.close() |
TIME_WAIT | 主动方最后ACK后 | 等待2MSL防ACK丢失 |
🔥 CLOSE_WAIT爆炸?
- 典型症状:服务器出现大量CLOSE_WAIT
- 致命原因:忘记关闭Socket!
// 错误示范:未关闭连接! Socket socket = new Socket(...); // 正确做法:try-with-resources自动关闭 try (Socket socket = new Socket(...)) { ... }
⏳ TIME_WAIT存在的意义
- 保活2MSL(约1-4分钟):确保最后一个ACK送达
- 副作用:短时内端口不可重用 → 可通过
SO_REUSEADDR
解决
📌 五、高频面试题
-
为什么是三次握手,不是两次?
– 确保双方收发能力正常 -
TIME_WAIT为何要等2MSL?
– 足够时间让网络中的残余报文消亡
掌握TCP协议,就像获得网络世界的“内功心法”!无论是面试还是调优,这些知识点都能让你一眼看穿网络问题的本质! 🚀