# TCP/IP 网络协议 ## 问题 1. TCP 和 UDP 的区别是什么? 2. TCP 三次握手和四次挥手的流程是什么?为什么需要三次握手? 3. TCP 如何保证可靠传输? 4. 什么是 SYN 洪水攻击?如何防范? 5. HTTP 和 HTTPS 的区别是什么? 6. HTTP 1.1、2.0、3.0 的演进历程和主要特性 7. 什么是粘包和拆包?如何解决? 8. 在实际项目中遇到过哪些网络问题? --- ## 标准答案 ### 1. TCP vs UDP #### **对比表** | 特性 | TCP | UDP | |------|-----|-----| | **连接性** | 面向连接 | 无连接 | | **可靠性** | 可靠传输 | 不可靠 | | **顺序** | 保证顺序 | 不保证顺序 | | **速度** | 较慢 | 快 | | **流量控制** | 有(滑动窗口) | 无 | | **拥塞控制** | 有 | 无 | | **首部开销** | 20-60 字节 | 8 字节 | | **应用场景** | 文件传输、邮件、HTTP | 视频流、直播、DNS | --- #### **TCP 特性** **面向连接**: ``` 客户端 服务器 │ │ │─────── SYN ───────────────→│ 建立连接 │←────── SYN+ACK ────────────│ │─────── ACK ───────────────→│ │ │ │─────── 数据 ───────────────→│ │←─────── ACK ───────────────│ │ │ │─────── FIN ───────────────→│ 断开连接 │←─────── ACK ───────────────│ │←────── FIN ────────────────│ │─────── ACK ───────────────→│ ``` **可靠传输**: - 确认应答(ACK) - 超时重传 - 校验和 - 序列号 **流量控制**: - 滑动窗口协议 - 动态调整窗口大小 --- #### **UDP 特性** **无连接、不可靠**: ``` 客户端 服务器 │ │ │─────── 数据报 ─────────────→│ (可能丢失) │─────── 数据报 ─────────────→│ (可能乱序) │ │ │ │ ``` **优点**: - 速度快(无连接、无确认) - 开销小(首部 8 字节) - 支持一对一、一对多、多对多 **应用场景**: - 视频直播(可容忍丢帧) - 在线游戏(实时性要求高) - DNS 查询(请求响应快) - VoIP(语音通话) --- ### 2. TCP 三次握手和四次挥手 #### **三次握手** **流程**: ``` 客户端 服务器 │ │ │─────── SYN=1, seq=x ──────→│ SYN_SENT │ │ │←───── SYN=1, ACK=1, seq=y, ack=x+1 ───│ │ SYN_RCVD │ │ │ │─────── ACK=1, seq=x+1, ack=y+1 ────→│ ESTABLISHED │ ESTABLISHED │ ``` **状态变化**: ``` 客户端:CLOSED → SYN_SENT → ESTABLISHED 服务器:CLOSED → LISTEN → SYN_RCVD → ESTABLISHED ``` --- #### **为什么需要三次握手?** **目的**: 1. **确认双方收发能力**: - 第一次握手:服务端确认客户端能发 - 第二次握手:客户端确认服务端能收能发 - 第三次握手:服务端确认客户端能收 2. **防止已失效的连接请求突然又传送到服务端**: ``` 场景:客户端发送的第一个连接请求在网络中滞留 结果:客户端超时重发,建立连接后,滞留的请求到达 如果只有两次握手: ─ 客户端发送 SYN ─ 服务器收到,建立连接,等待数据 ─ 滞留的 SYN 到达,服务器又建立连接(错误!) 三次握手避免: ─ 服务器收到滞留的 SYN,回复 SYN+ACK ─ 客户端发现 ack 不对,丢弃(不建立连接) ``` 3. **同步初始序列号(ISN)**: - 双方协商好初始序列号 - 保证数据的顺序和去重 --- #### **四次挥手** **流程**: ``` 客户端 服务器 │ │ │─────── FIN=1, seq=u ──────→│ FIN_WAIT1 │ │ CLOSE_WAIT │←─────── ACK=1, seq=v, ack=u+1 ───│ │ FIN_WAIT2 │ │ │ │←─────── FIN=1, ACK=1, seq=w, ack=u+1 ───│ │ TIME_WAIT │ LAST_ACK │ │ │─────── ACK=1, seq=u+1, ack=w+1 ────→│ CLOSED │ (等待 2MSL) │ ``` **状态变化**: ``` 客户端:ESTABLISHED → FIN_WAIT1 → FIN_WAIT2 → TIME_WAIT → CLOSED 服务器:ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED ``` --- #### **为什么需要四次挥手?** **原因**: - TCP 是**全双工**协议(双向传输) - 双方都需要关闭发送方向 **流程**: 1. 客户端发送 FIN(关闭客户端→服务器方向) 2. 服务器确认 ACK(但服务器可能还有数据要发送) 3. 服务器发送 FIN(关闭服务器→客户端方向) 4. 客户端确认 ACK --- #### **为什么 TIME_WAIT 状态需要等待 2MSL?** **MSL(Maximum Segment Lifetime)**: - 报文最大生存时间(通常 30 秒 - 2 分钟) - 2MSL = 1 分钟 - 4 分钟 **目的**: 1. **确保最后一个 ACK 能到达**: - 如果 ACK 丢失,服务器会重传 FIN - 客户端等待 2MSL,可以重传 ACK 2. **让旧连接的报文自然消失**: - 防止旧连接的报文干扰新连接 - 确保网络中所有旧报文都已消失 **问题**: - 大量 TIME_WAIT 会导致端口资源耗尽 - 解决:调低 `TIME_WAIT` 时间或端口复用 ```bash # Linux 调整 net.ipv4.tcp_tw_reuse = 1 # 端口复用 net.ipv4.tcp_tw_recycle = 0 # 关闭快速回收(有坑) ``` --- ### 3. TCP 可靠传输机制 #### **核心机制** 1. **序列号(Sequence Number)**: - 每个字节都有编号 - 用于排序和去重 2. **确认应答(ACK)**: - 接收方确认收到的数据 - ACK = 下一个期望接收的字节序号 ``` 发送方:seq=1000, len=100 接收方:ack=1100(期待下一个字节) ``` 3. **超时重传(RTO)**: - 发送方启动定时器 - 超时未收到 ACK,重传数据 4. **快速重传**: - 收到 3 个重复 ACK,立即重传 - 无需等待超时 ``` 发送方:seq=1000, len=100 接收方:期望 1100,但收到 1200(乱序) 接收方:连续发送 ack=1100(3 次) 发送方:收到 3 个重复 ack,快速重传 ``` 5. **滑动窗口**: - 流量控制 - 动态调整发送速率 6. **拥塞控制**: - 慢启动、拥塞避免、快重传、快恢复 --- ### 4. SYN 洪水攻击 #### **攻击原理** **场景**:攻击者发送大量 SYN 包,但不完成三次握手。 ``` 攻击者 服务器 │ │ ├─── SYN ─────────────────────→│ 分配资源 ├─── SYN ─────────────────────→│ 分配资源 ├─── SYN ─────────────────────→│ 分配资源 ├─── SYN ─────────────────────→│ 分配资源 │ (不回复 ACK) │ 资源耗尽 │ │ 无法服务正常用户 ``` **后果**: - 服务器维护大量 `SYN_RCVD` 连接 - 内存资源耗尽 - 无法响应正常用户的连接请求 --- #### **防范措施** **1. SYN Cookies**: ```bash # 开启 SYN Cookies net.ipv4.tcp_syncookies = 1 ``` **原理**: - 不分配资源 - 计算 Cookie 并编码在 SYN+ACK 的初始序列号中 - 客户端回复 ACK 时验证 Cookie **2. 增加 SYN 队列**: ```bash net.ipv4.tcp_max_syn_backlog = 8192 ``` **3. 缩短超时时间**: ```bash net.ipv4.tcp_synack_retries = 2 ``` **4. 防火墙**: - 限制单个 IP 的连接数 - 检测异常流量并拦截 --- ### 5. HTTP vs HTTPS #### **对比** | 特性 | HTTP | HTTPS | |------|------|-------| | **协议** | 应用层 | 应用层 + 安全层(SSL/TLS) | | **端口** | 80 | 443 | | **加密** | 明文传输 | 加密传输 | | **证书** | 不需要 | 需要 CA 证书 | | **性能** | 快 | 较慢(TLS 握手) | | **SEO** | 无优惠 | 搜索引擎优先排名 | --- #### **HTTPS 工作流程** ``` 客户端 服务器 │ │ │─────── ClientHello ────────────→│ (支持的加密套件) │ │ │←────── ServerHello ─────────────│ (选择的加密套件 + 证书) │←────── Certificate ─────────────│ │ │ │─────── ClientKeyExchange ─────→│ (生成随机数) │─────── ChangeCipherSpec ───────→│ (之后消息加密) │─────── Finished ───────────────→│ │ │ │←────── ChangeCipherSpec ────────│ │←────── Finished ────────────────│ │ │ │══════ 加密通信 ══════════════════│ ``` --- #### **HTTPS 的安全性** 1. **数据加密**:防止中间人窃听 2. **数据完整性**:防止数据被篡改 3. **身份认证**:防止钓鱼网站 --- ### 6. HTTP 版本演进 #### **HTTP/1.0** **特点**: - 每个请求都需要新的 TCP 连接 - 无状态、无连接 **问题**: - 性能差(频繁建立连接) - 队头阻塞 --- #### **HTTP/1.1** **改进**: 1. **持久连接**:`Connection: keep-alive` 2. **管道化(Pipelining)**:可发送多个请求 3. **分块传输**:`Transfer-Encoding: chunked` 4. **缓存**:更强的缓存控制 **问题**: - 队头阻塞仍然存在 - 请求串行执行 --- #### **HTTP/2.0** **改进**: 1. **二进制协议**:不再是纯文本 2. **多路复用**:一个 TCP 连接并发多个请求 3. **头部压缩**:HPACK 算法 4. **服务端推送**:Server Push **多路复用**: ``` HTTP/1.1: 请求1 → ━━━ 连接1 ━━━ → 响应1 请求2 → ━━━ 连接2 ━━━ → 响应2 请求3 → ━━━ 连接3 ━━━ → 响应3 HTTP/2.0: 请求1 ┓ 请求2 ┣━━━━ 单连接 ━━━→ ┳ 响应1 请求3 ┛ ┻ 响应2 响应3 ``` --- #### **HTTP/3.0(QUIC)** **改进**: 1. **基于 UDP**:不再是 TCP 2. **解决队头阻塞**:流级别隔离 3. **更快握手**:0-RTT 4. **连接迁移**:IP 变化不影响连接 **架构**: ``` HTTP/3.0 ↓ QUIC(UDP) ↓ 加密、可靠传输、流控制 ``` --- ### 7. 粘包和拆包 #### **问题** **TCP 是字节流协议**,无消息边界: ``` 发送方发送两个包: ┌──────┐ ┌──────┐ │ 包1 │ │ 包2 │ └──────┘ └──────┘ 接收方可能收到: 1. 正常:┌──────┐┌──────┐ 2. 粘包:┌──────┐┌──────┐(两个包粘在一起) 3. 拆包:┌────┐ ┌──┐┌──┐┌────┐(包被拆散) ``` --- #### **解决方案** **1. 固定长度**: ``` 每个包固定 100 字节 优点:简单 缺点:浪费空间(短消息) ``` **2. 分隔符**: ``` 每个包以 \n 结束 优点:实现简单 缺点:内容中不能有分隔符 ``` **3. 长度字段(推荐)**: ``` ┌────┬──────────┐ │长度│ 数据 │ └────┴──────────┘ 实现:Netty 的 LengthFieldBasedFrameDecoder ``` **Netty 示例**: ```java // 服务端 public class Server { public static void main(String[] args) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { // 解决粘包拆包:长度字段解码器 ch.pipeline().addLast(new LengthFieldBasedFrameDecoder( 1024 * 1024, // 最大帧长度 4, // 长度字段偏移量 4, // 长度字段长度 0, // 长度调整值 4 // 剥离的字节数 )); ch.pipeline().addLast(new MessageHandler()); } }); bootstrap.bind(8080).sync().channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } // 消息编解码 public class MessageEncoder extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) { byte[] data = msg.getBody().getBytes(); out.writeInt(data.length); // 长度字段 out.writeBytes(data); // 数据字段 } } ``` --- ### 8. 实际项目经验 #### **案例 1:TCP 长连接异常断开** **问题**: - 客户端崩溃,服务器未收到 FIN - 服务器维护大量死连接 **解决**: ```java // 启用 TCP Keep-Alive socket.setKeepAlive(true); // 应用层心跳 @Scheduled(fixedRate = 30000) public void sendHeartbeat() { channel.writeAndFlush(new HeartbeatMessage()); } // 超时断开 ch.pipeline().addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS)); ch.pipeline().addLast(new HeartbeatHandler()); ``` --- #### **案例 2:HTTPS 性能优化** **问题**: - HTTPS 握手耗时 200ms+ - 高并发下性能差 **解决**: 1. **HTTP/2 多路复用** 2. **TLS False Start**:减少 1 个 RTT 3. **Session Resumption**:恢复会话 4. **HSTS**:强制 HTTPS,避免重定向 --- ### 9. 阿里 P7 加分项 **深度理解**: - 理解 TCP 的拥塞控制算法( Reno、Cubic、BBR) - 理解 QUIC 协议的设计原理 - 理解 TLS 1.3 的改进(0-RTT、加密握手) **实战经验**: - 有处理网络抖动导致的连接不稳定问题 - 有 TCP 参数调优经验 - 有 HTTPS 性能优化经验 **架构能力**: - 能设计高性能的网络通信框架 - 能设计跨地域的网络架构 - 有网络监控和故障排查经验 **技术选型**: - 了解 gRPC、Thrift 等 RPC 框架 - 了解 Netty、Mina 等网络框架 - 能根据业务特点选择合适的协议