《Netty 高性能网络编程》10.1 WebSocketServerProtocolHandler
WebSocket 仍以 HTTP/1.1 Upgrade 开始。本节按 HttpServerCodec、HttpObjectAggregator、WebSocketServerProtocolHandler 的顺序装配 9090 端口,讲路径匹配、握手完成事件与 400 失败分支,并给出可运行的 WebSocketServer 与 curl 验证命令。
tag
WebSocket 仍以 HTTP/1.1 Upgrade 开始。本节按 HttpServerCodec、HttpObjectAggregator、WebSocketServerProtocolHandler 的顺序装配 9090 端口,讲路径匹配、握手完成事件与 400 失败分支,并给出可运行的 WebSocketServer 与 curl 验证命令。
AUTO_READ 默认开启时,Netty 读完一轮就自动继续读,业务队列满也不会停。本节用本机输出验证手动 read() 的一轮边界,讲清 channelReadComplete 的恢复时机、慢消费者队列上限与 RCVBUF_ALLOCATOR 的边界,并给出 linkd-v2 的 ReadBackpressureHandler 与流量整形取舍。
write() 与 writeAndFlush() 的差别不在名字,而在 ChannelFuture 什么时候完成。本节用本机输出验证入队与 flush 的分界,解释批量写后一次 flush 的收益、待写队列在不可写状态下继续增长的风险,并给出 linkd-v2 广播发布器的批量写与监听器重试、丢弃策略。
WriteBufferWaterMark 的两个参数到底在统计什么?本节用 linkd-v2 的 WritabilityPump 拆开 Channel.isWritable 的判定、channelWritabilityChanged 的触发与 4.2 默认的 32/64 KiB 水位,给出生产者暂停与恢复的完整 handler,并用本机输出核对 config 打印值与状态迁移。
关闭一条连接有 close、disconnect、shutdownOutput 三种语义,半关闭处理错会留下永远不释放的连接。本节说明 ALLOW_HALF_CLOSURE 与 ChannelInputShutdownEvent 的配合,用 ChannelGroup 批量关闭,给出 linkd-v2 GracefulShutdown 的步骤与期限,以及 SO_LINGER 触发 RST 的边界。
连接池的关键不是池化本身,而是何时借、何时还、何时判定连接已坏。本节拆开 FixedChannelPool 的三个生命周期回调,配合 ChannelHealthChecker 与获取超时,用 AbstractChannelPoolMap 按 host:port 建池,并用 Little 定律说明池大小以及 HTTP/1.1 与 HTTP/2 的取舍。
客户端重连的难点不在 connect 调用,而在失败路径、触发时机与退避节奏。本节拆开 Bootstrap.connect 的拒绝、超时与握手失败三种结果,用 closeFuture 触发重连,给出 linkd-v2 ReconnectHandler 的状态机、指数退避、0.8 到 1.2 抖动、排队上限与惊群规避。
9093 上任何能连上的客户端都能发 REQUEST。本节用 HELLO/token 三态握手把未认证连接挡在业务 handler 之前:AttributeKey 与 Channel 绑定、MessageDigest.isEqual 比较 token、10 秒认证超时、失败即 ERROR 加 close。
连接空闲时双方都不知道对端是否还活着。本节拆开 IdleStateHandler 的三种空闲与 IdleStateEvent.state(),给出服务端 30 秒发 PING、客户端回 PONG、连续两轮无响应关闭的完整 handler,并说明心跳为什么替代不了业务超时。
16 字节头里的每个字段都有代价。本节讲清 magic 为什么不是安全措施、version 怎么兼容、type 与 flags 留了多少扩展位、requestId 的无符号语义和 1 MiB 上限,并给出 LinkdFrame 与两个编解码器的完整实现。
解码失败不是一句 catch 就能收尾。本节讲 DecoderException、TooLongFrameException、CorruptedFrameException 在 pipeline 里的传播路径,给出 linkd-v2 的 ERROR 帧与关闭策略、HTTP decoderResult 的 400 处理,并说明 exceptionCaught 吞掉异常后连接为什么会半死。
LengthFieldBasedFrameDecoder 的五个参数最容易配错。本节用三组真实字节序列算清 lengthFieldOffset、lengthFieldLength、lengthAdjustment 与 initialBytesToStrip,给出 linkd-v2 十六字节头 strip 16 的配置,并对比 LengthFieldPrepender、分隔符与定长解码器。
粘包拆包都从 ByteToMessageDecoder 开始。本节拆开它的 cumulation 缓冲、callDecode 循环、decodeLast 与 isSingleDecode,给出可运行的行解码器和 linkd-v2 帧解码器,并说明 decode 里 release 入参、读不够却推进 readerIndex、以及用累积缓冲承载超大帧会付出什么代价。
拆开 PooledByteBufAllocator 的 arena、thread cache、chunk、page 结构,用 metric() 读出 arena 分配数与线程缓存命中,验证 4.2 里 ByteBufAllocator.DEFAULT 的真实类型,并给出 linkd-v2 的分配器配置与指标打印代码。
从 readRetainedSlice 的释放责任讲起,给出 retain/release 的所有权规则,说明 SimpleChannelInboundHandler 与 ByteToMessageDecoder 的释放边界,用一个静态 Map 反例复现 LEAK 日志,并整理 paranoid 级别的排查步骤与 ReferenceCountUtil 的使用范围。
用 Unpooled.buffer 把 readerIndex、writerIndex、capacity、maxCapacity 的每次变化跑成表,对照 JDK ByteBuffer 的 position 与 limit,讲清 readableBytes 为什么不能当消息长度,以及 slice、duplicate、retainedSlice 谁共享内容、谁增加引用计数。
本节用 linkd-v2 的 HandshakeHandler 说明哪些字段让 handler 变成有状态,@Sharable 在添加时检查什么,共享实例会带来哪些并发与串状态风险,并给出 AttributeKey、ChannelGroup 与不可变 LinkdFrame 的正确用法,以及 DefaultEventExecutorGroup 的能力边界。
本节按时间顺序拆开 handlerAdded、channelRegistered、channelActive、channelRead、channelReadComplete、channelInactive 与 handlerRemoved,说明 ChannelInitializer 如何移除自己,并用 HandshakeHandler 演示每连接状态迁移。
本节拆开 linkd-v2 的两条 pipeline:head/tail 哨兵、入站与出站方向、addLast 顺序为何决定 HttpServerCodec、HttpObjectAggregator 与业务 handler 的先后,以及 ctx.writeAndFlush() 与 channel().writeAndFlush() 的起点差异。
本节解释为什么 JDBC、文件 IO 和同步 HTTP 不能放在 EventLoop 上,并用本机输出对比三种隔离:DefaultEventExecutorGroup、独立线程池、JDK 25 虚拟线程。给出 GatewayHandler 把转发任务提交到虚拟线程再回写 Channel 的完整代码,以及虚拟线程在 CPU 密集和下游容量上的边界。