计算机网络:TCP、HTTP 与 Netty

从握手挥手、可靠传输到 HTTP/2、HTTPS 和 Netty 的线程模型,梳理网络面试的高频考点与边界

网络题看起来零散,其实可以围绕「连接怎么建立、数据怎么可靠送达、协议怎么演进、框架怎么封装」四条线来准备。

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 的缓冲区组合。

参考资料

评论

为保障内容质量,评论需站长审核通过后才会展示;提交后没立刻看到属正常,请勿重复提交。

还没有评论,来抢沙发~