网络题看起来零散,其实可以围绕「连接怎么建立、数据怎么可靠送达、协议怎么演进、框架怎么封装」四条线来准备。
1. TCP 为什么是三次握手、四次挥手?
三次握手:客户端发 SYN,服务端回 SYN-ACK,客户端再回 ACK。两次不够,是因为服务端无法确认客户端收到了自己的 SYN;同时也能防止网络中滞留的旧连接请求突然到达,造成错误连接。第三次 ACK 让双方都确认「收发能力正常、初始序列号已同步」。
四次挥手是因为 TCP 是全双工的:主动方发 FIN 只表示「我没有数据要发了」,对方还可以继续发送,所以要先回 ACK,等自己的数据发完再发 FIN。主动方收到对方的 FIN 后回 ACK,连接才完全关闭。
主动关闭方会进入 TIME_WAIT,等待 2MSL。目的有两个:保证最后一个 ACK 能到达对方,否则对方会重发 FIN;让本连接中的旧报文在网络中消失,避免影响使用相同四元组的新连接。
高并发短连接的服务端容易出现大量 TIME_WAIT,解决方向是连接复用、长连接,而不是盲目调小等待时间。
2. TIME_WAIT 和 CLOSE_WAIT 分别意味着什么?
TIME_WAIT 出现在主动关闭方,是正常的收尾状态,通常持续 2MSL。数量很大往往说明短连接太多,或者服务端主动关闭了连接;优先考虑连接池和 keep-alive。
CLOSE_WAIT 出现在被动关闭方,表示「对方已经发了 FIN,但本端应用还没有调用 close」。大量 CLOSE_WAIT 几乎总是代码问题:连接没有在 finally 中关闭、异常路径漏了释放、连接池配置不当。
排查时用 ss -tan state close-wait 或 netstat 看数量,再结合进程和线程栈定位是哪段代码持有连接。CLOSE_WAIT 不会自己消失,必须由应用关闭。
还要区分 ESTABLISHED 过多(并发高或泄漏)和 SYN_RECV 过多(可能的 SYN Flood)。
3. TCP 如何保证可靠传输?
每个字节都有序号,接收方用累积确认告诉发送方「我已经收到了哪里」。超时未确认会重传;收到三个重复 ACK 时触发快速重传,不必等到超时。校验和用于发现传输错误。
滑动窗口做流量控制:接收方通过窗口字段告诉发送方还能接收多少,避免发送过快压垮接收端。窗口扩大选项让高带宽高延迟链路也能有足够吞吐。
拥塞控制则根据网络状况调整发送速率,包括慢启动、拥塞避免、快速恢复等阶段。现代内核常用 CUBIC,部分场景用 BBR,它们对丢包和延迟的假设不同。
TCP 保证的是「可靠、有序的字节流」,不保证应用层消息边界,也不保证实时性。UDP 不建立连接、不重传,适合实时音视频、DNS 这类能容忍少量丢包的场景。
4. 粘包拆包是怎么产生的?怎么解决?
TCP 是字节流,没有消息边界。发送方两次 write 的数据可能被合并成一个包到达,也可能一条消息被拆成多个包,这就是「粘包」和「拆包」。UDP 有数据报边界,不存在这个问题,但会丢包和乱序。
解决办法是在应用层定义边界:定长消息、分隔符、或者最常用的「长度字段 + 消息体」。长度字段方案最稳妥,能处理任意二进制内容,也是大多数 RPC 协议的选择。
Netty 提供了现成的解码器:LengthFieldBasedFrameDecoder 处理长度前缀,DelimiterBasedFrameDecoder 处理分隔符,FixedLengthFrameDecoder 处理定长。还要配合 IdleStateHandler 处理空闲连接。
注意长度字段的字节序、最大长度限制和半包缓存,恶意超长长度可能造成内存耗尽。
5. HTTP/1.1、HTTP/2、HTTP/3 有什么区别?
HTTP/1.1 支持持久连接,但一个连接同一时刻通常只处理一个请求,浏览器靠开多个连接提升并发;管道化理论上可以连续发请求,但因队头阻塞和代理兼容问题很少使用。
HTTP/2 引入二进制分帧和多路复用,一个 TCP 连接上可以并行多个流,头部用 HPACK 压缩,减少了连接数和头部开销。但 TCP 层仍有队头阻塞:一个包丢失,所有流都要等重传。
HTTP/3 基于 QUIC(UDP),流之间独立,丢包只影响所在流;内置 TLS 1.3,支持 0-RTT 恢复,连接迁移也更平滑。代价是 UDP 在部分网络中被限制,运维和排查方式与 TCP 不同。
选型时还要考虑中间设备和服务端支持,HTTP/2 的收益在高延迟、多资源场景最明显。
6. HTTPS 的握手过程是怎样的?
客户端先发 ClientHello,包含支持的协议版本、加密套件和随机数;服务端回 ServerHello 和证书链。客户端验证证书:是否由受信任 CA 签发、域名是否匹配、是否在有效期内、是否被吊销。
验证通过后,双方通过密钥交换算法协商出共享密钥。现代 TLS 使用 ECDHE,具备前向安全:即使服务器私钥泄露,也无法解密过去的会话。之后用对称加密传输数据,因为对称加密性能更好。
TLS 1.3 把握手压缩到 1-RTT,并支持会话恢复时的 0-RTT,但 0-RTT 数据存在重放风险,只适合幂等请求。
中间人攻击的前提是客户端信任了攻击者的证书,所以证书校验、HSTS 和证书透明度都很重要。HTTPS 保护传输,不保护服务端被入侵后的数据。
7. Cookie、Session、Token、JWT 怎么选?
Cookie 是浏览器存储机制,HttpOnly 防止脚本读取,Secure 只走 HTTPS,SameSite 缓解 CSRF。Session 把状态放在服务端,浏览器只存 session id,撤销方便,但需要共享存储。
Token 是广义的凭证,JWT 是其中一种自包含格式:头部、载荷、签名。服务端验签即可,不必查存储,适合水平扩展;但载荷是明文可读的,不能放敏感信息,撤销也需要额外机制。
常见组合是「短期 access token + 长期 refresh token」,敏感操作再校验权限或黑名单。把 JWT 放在 localStorage 容易受 XSS 影响,放在 HttpOnly Cookie 又要防 CSRF,需要配套措施。
选择的核心是状态放在哪、撤销要求多强、是否有跨域和跨端需求,而不是「JWT 更现代」。
8. Netty 为什么高性能?零拷贝是什么?
Netty 用主从 Reactor 模型:boss 线程组负责接收连接,worker 线程组负责读写事件;每个 Channel 绑定一个 EventLoop,事件在单线程内串行处理,避免锁竞争。
它基于 NIO 多路复用,支持海量连接;ByteBuf 用读指针和写指针分离读写位置,支持池化和堆外内存,减少分配与复制;Pipeline 用责任链组织编解码和业务处理,扩展方便。
「零拷贝」在不同语境下含义不同:操作系统层面是 sendfile 让数据不经过用户态;Netty 层面更多是 CompositeByteBuf 组合多个缓冲区、FileRegion 传输文件、以及用堆外内存减少 JVM 堆与内核之间的复制。它不等于「完全不拷贝」。
使用时要防止在 EventLoop 里执行阻塞操作,否则会卡住该线程上的所有连接;还要注意 ByteBuf 的引用计数和泄漏检测,堆外内存泄漏不会体现在堆 dump 里。
易错点与自测
- 三次握手不是为了「确认双方在线」这么简单,核心是序列号同步和防止旧连接。
TIME_WAIT是主动关闭方的正常状态,CLOSE_WAIT堆积多半是应用没关连接。- HTTP/2 解决了 HTTP 层队头阻塞,没有解决 TCP 层;HTTP/3 才换掉 TCP。
自测:① 解释 2MSL 的两个作用;② 说明长度字段解码器如何处理半包;③ 区分 sendfile 零拷贝与 Netty 的缓冲区组合。
参考资料
- RFC 9293:TCP — 连接管理、重传与流量控制的规范
- RFC 9110:HTTP 语义 — 方法、状态码与连接语义
- RFC 9000:QUIC — HTTP/3 的传输基础
- Netty 4.x 用户指南 — Reactor 模型、Pipeline 与编解码器