一、什么是 TCP 协议?
TCP (Transmission Control Protocol,传输控制协议) 是 OSI 模型中传输层的核心协议。
1. 核心特点(面试必背)
- 面向连接 (Connection-Oriented):传输数据前必须先建立连接(三次握手),传输结束后释放连接(四次挥手)。
- 可靠传输 (Reliable):保证数据无差错、不丢失、不重复、且按序到达。
- 字节流服务 (Byte Stream):TCP 把数据看成一连串无结构的字节流,不保留报文边界(这导致了 Java 中的"粘包/拆包"问题)。
- 全双工通信 (Full-Duplex):允许通信双方的应用进程在任何时候都能发送数据。
- 点对点 (Point-to-Point):每条 TCP 连接只能是两个端点。
2. TCP vs UDP (简单对比)
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠 (确认、重传) | 不可靠 (尽最大努力交付) |
| 速度 | 较慢 (头部大、机制多) | 快 (头部小、无拥塞控制) |
| 场景 | HTTP, HTTPS, SSH, 数据库连接 | 视频直播,DNS, 实时游戏 |
二、TCP 报文头格式 (TCP Header)
TCP 头部最小 20 字节 ,最大 60 字节(因为有可选字段)。理解头部字段对于使用 Wireshark 抓包分析问题至关重要。
1. 结构图解
图来自4.1 TCP 三次握手与四次挥手面试题 | 小林coding | Java面试学习
2. 关键字段详解 (Java 后端需关注)
- 源端口 & 目的端口 (16 位) :
- 标识主机上的应用程序。Java 中
ServerSocket绑定的就是目的端口,Socket连接时会分配随机源端口。
- 标识主机上的应用程序。Java 中
- 序列号 (Seq, 32 位) :
- 作用:解决"乱序"问题。TCP 给每个字节编号,接收方根据 Seq 重组数据。
- 初始值:连接建立时随机生成 (ISN),防止历史连接干扰。
- 确认号 (Ack, 32 位) :
- 作用:解决"丢失"问题。表示期望收到对方下一个报文段的第一个数据字节的序号。
- 规则 :只有当标志位
ACK=1时,该字段才有效。
- 数据偏移 (4 位) :
- 指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远(即头部长度)。单位是 4 字节,所以最大头部是 15×4=6015×4=60 字节。
- 标志位 (Flags, 6 位) ------ 抓包分析核心 :
- URG: 紧急指针有效(很少用)。
- ACK: 确认号有效。建立连接后所有包该位都为 1。
- PSH (Push) : 提示接收方尽快将数据交给应用层,不要缓存(Java 中
socket.setTcpNoDelay(true)相关)。 - RST (Reset): 重置连接。当出现错误(如连接不存在)时,发送 RST 强行断开。
- SYN (Synchronize) : 同步序列号。建立连接时使用(三次握手的前两次)。
- FIN (Finish) : 释放连接。断开连接时使用(四次挥手)。
- 窗口 (Window, 16 位) :
- 作用:流量控制。告诉对方"我还能接收多少字节的数据"。防止发送方发太快,接收方处理不过来。
- 校验和 (Checksum) :
- 保证数据在传输过程中没有损坏。
三、TCP vs UDP 核心区别对比
| 对比维度 | TCP | UDP |
|---|---|---|
| 全称 | Transmission Control Protocol | User Datagram Protocol |
| 连接性 | 面向连接(需三次握手) | 无连接(直接发送) |
| 可靠性 | 可靠传输(确认+重传) | 不可靠(尽最大努力交付) |
| 数据顺序 | 保证顺序到达 | 不保证顺序 |
| 数据边界 | 字节流(无边界,易粘包) | 数据报(保留边界) |
| 传输速度 | 较慢(头部大、机制多) | 快(头部小、开销少) |
| 头部大小 | 最小 20 字节 | 固定 8 字节 |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有(慢启动、拥塞避免) | 无 |
| 通信模式 | 点对点(1对1) | 支持单播、广播、多播 |
| Java 类 | Socket / ServerSocket |
DatagramSocket / DatagramPacket |


四、Java 后端开发者特别关注点
作为 Java 后端,你不需要手写 TCP 包,但你需要处理 TCP 之上的逻辑。
1. 粘包与拆包 (Sticky/Unsticky Packet)
- 现象 :TCP 是流式协议 ,没有消息边界。
- 粘包 :发送方发了两个包
A和B,接收方一次性读到了AB。 - 拆包 :发送方发了一个大包
A,接收方分两次读到了A1和A2。
- 粘包 :发送方发了两个包
- 原因:TCP 为了提高效率,会将小数据包合并(Nagle 算法),或者受限于 MSS (最大报文段长度) 进行拆分。
- Java 解决方案 :
- 定长:每个消息固定长度。
- 分隔符 :如 HTTP 用
\r\n\r\n,Redis 协议用\r\n。 - 长度字段 :在消息头加一个
int表示 body 长度(最常用,Netty 的LengthFieldBasedFrameDecoder)
2. 心跳机制 (Keep-Alive)
- 问题 :如果网线断了,或者对端进程挂了,TCP 连接可能一直处于
ESTABLISHED状态(死连接)。 - OS 级 Keepalive:默认开启但时间太长(2 小时),不适合业务。
- 应用级心跳 :Java 后端通常自己实现。
- 客户端每隔 30 秒发一个
PING包。 - 服务端收到回
PONG。 - 如果超过 90 秒没收到
PING,服务端主动关闭连接,释放资源。 - Netty 中提供了
IdleStateHandler轻松实现。
- 客户端每隔 30 秒发一个
3. 性能调优参数 (Linux 内核)
在部署 Java 应用的高并发服务器上,通常需要调整 /etc/sysctl.conf:
net.core.somaxconn: 监听队列最大长度(对应ServerSocket的 backlog 参数)。net.ipv4.tcp_tw_reuse: 允许重用 TIME_WAIT 状态的 socket(解决端口耗尽)。net.ipv4.tcp_keepalive_time: 调整心跳检测时间。
五、总结与建议
- 理解本质:TCP 是为了在不可靠的网络上构建可靠的通道,它的头部字段(Seq/Ack/Window)都是为了这个目标服务的。
- 工具使用 :学会使用 Wireshark 或 tcpdump 抓包。当 Java 程序网络卡顿时,看包是 SYN 发不出去,还是 ACK 没回来,能迅速定位是网络问题还是代码问题。
- 框架选择 :
- 简单场景:使用
java.net.Socket(BIO)。 - 高并发场景:使用 Netty (NIO)。Netty 帮你处理了 TCP 的粘包拆包、心跳、断线重连等复杂细节,但底层原理依然是 TCP。
- 简单场景:使用
- 面试准备:重点准备三次握手、四次挥手、TIME_WAIT/CLOSE_WAIT 区别、粘包拆包解决方案。