《Netty 高性能网络编程》附录 A Netty 常用 API 速查

按 Bootstrap、Channel、EventLoop、Pipeline、ByteBuf、编解码、HTTP、WebSocket、HTTP/2 与连接池十组,列出 Netty 4.2 常用类、正确包名、方法签名与对应章节,并标出容易记错的常量与重载差异,供写 linkd-v2 时直接查表。

这张表怎么用

这不是教程,是把正文里出现过的类、包名和方法签名压成一张可查的表。写 linkd-v2 时先在这里定位类,再回到对应章节看为什么这样用。

约定如下:

  • 版本固定为 Netty 4.2.19.Final,JDK 25。方法签名省略返回值类型,只保留参数,避免表格过宽;需要返回值时看「用途」一列。
  • 「包」一列是完整包名。Netty 的类分散在 netty-transport、netty-buffer、netty-codec-http 等模块里,netty-all 只是聚合 POM,import 写错包是编译期最常见的错误。
  • 「章节」指向正文的节号,附录不重复解释机制。
  • 表格里的 ... 表示可变参数或省略的重载,不是可以照抄的省略号。

包名先单独记四个容易混的:

类正确包常见错写
ChannelGroupio.netty.channel.groupio.netty.channel
NioEventLoopGroupio.netty.channel.nioio.netty.channel
Epoll / KQueueio.netty.channel.epoll / io.netty.channel.kqueueio.netty.channel.socket
Http2FrameCodecBuilderio.netty.handler.codec.http2io.netty.handler.codec.http

ChannelGroup 不在 io.netty.channel 下,这一点在 IDE 自动补全时经常被选错;Epoll 与 KQueue 都只在对应平台的 native 构件存在时才可用,import 能通过不代表运行期能加载。

Bootstrap 与启动

ServerBootstrap 管服务端,Bootstrap 管客户端。两者的方法名高度对称,区别在服务端要分 boss/worker 两组线程、要设 childHandler 和 childOption,客户端只有一个 handler。bind 与 connect 都返回 ChannelFuture,但不要在 EventLoop 线程里对它调用 sync()。

类包成员用途章节
ServerBootstrapio.netty.bootstrapgroup(EventLoopGroup parent, EventLoopGroup child)设置接受连接的 boss 组与处理 IO 的 worker 组2.1
ServerBootstrapio.netty.bootstrapchannel(Class<? extends ServerChannel> cls)指定服务端 Channel 实现,如 NioServerSocketChannel2.1
ServerBootstrapio.netty.bootstrapchildHandler(ChannelHandler handler)每条新连接初始化时调用的 handler2.1
ServerBootstrapio.netty.bootstrapoption(ChannelOption<T> opt, T value)作用于监听 socket 的参数,如 SO_BACKLOG2.3
ServerBootstrapio.netty.bootstrapchildOption(ChannelOption<T> opt, T value)作用于每条已接受连接的参数,如 TCP_NODELAY2.3
ServerBootstrapio.netty.bootstrapbind(int port) / bind(SocketAddress local)绑定端口,返回 ChannelFuture2.1
Bootstrapio.netty.bootstrapgroup(EventLoopGroup group)客户端只设一组 EventLoop8.1
Bootstrapio.netty.bootstrapchannel(Class<? extends Channel> cls)指定客户端 Channel,如 NioSocketChannel8.1
Bootstrapio.netty.bootstraphandler(ChannelHandler handler)客户端 pipeline 的初始化 handler8.1
Bootstrapio.netty.bootstrapconnect(String host, int port) / connect(SocketAddress remote)发起连接,返回 ChannelFuture8.1
Bootstrapio.netty.bootstrapremoteAddress(SocketAddress remote)给连接池复用同一远端地址时使用8.2

option 与 childOption 的分工可以用一句话判断:连接建立之前就要生效的( backlog、地址复用)放 option;每条连接自己的(TCP_NODELAY、水位线、AUTO_READ)放 childOption。把 TCP_NODELAY 写到 option 里,编译能过,运行期不生效。

一个最小服务端骨架,把上表串起来:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public final class BootstrapSketch {
    public static void start() throws InterruptedException {
        ServerBootstrap b = new ServerBootstrap();
        b.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childOption(ChannelOption.TCP_NODELAY, true)
                .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK,
                        new WriteBufferWaterMark(32 * 1024, 64 * 1024))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        // 这里挂 LinkdFrameDecoder / LinkdFrameEncoder
                    }
                });
        ChannelFuture f = b.bind(9093).sync();
        f.channel().closeFuture().sync();
    }
}

这段代码只演示装配顺序,没有挂真实 handler。bind(...).sync() 在主线程调用没问题;如果把它搬进某个 handler 的 channelRead 里,就会卡住该 Channel 所属的 EventLoop。

Channel、Future 与 Socket 参数

Channel 是连接的门面:读写、关闭、取 pipeline、取 EventLoop 都从这里进。ChannelFuture 是异步结果,sync() 只适合启动线程和测试线程;在 EventLoop 线程里用 addListener。ChannelOption 是强类型常量,不是字符串,写 option("TCP_NODELAY", true) 编译不过。

类包成员用途章节
Channelio.netty.channelid()连接的唯一标识,日志里用它关联一次会话2.2
Channelio.netty.channelisActive() / isOpen()是否已连接、是否尚未关闭2.2
Channelio.netty.channelisWritable()写缓冲是否低于高水位,背压判断入口9.1
Channelio.netty.channelconfig()取 ChannelConfig,运行期改 AUTO_READ 等9.3
Channelio.netty.channelpipeline()取管道,动态增删 handler4.1
Channelio.netty.channeleventLoop()取绑定的 EventLoop,用于判断当前线程3.1
Channelio.netty.channelalloc()取该 Channel 的 ByteBufAllocator5.3
Channelio.netty.channelread()在 AUTO_READ=false 时主动请求一次读9.3
Channelio.netty.channelwrite(Object msg)写到 pipeline,尚未 flush9.2
Channelio.netty.channelwriteAndFlush(Object msg)写并 flush,返回 ChannelFuture9.2
Channelio.netty.channelclose() / closeFuture()主动关闭、监听关闭完成8.3
Channelio.netty.channelparent()子 Channel 取父 Channel,HTTP/2 stream 常用11.1
Channelio.netty.channelremoteAddress() / localAddress()对端与本地地址,日志与连接表使用2.2
ChannelFutureio.netty.channelchannel()从 future 取回 Channel2.2
ChannelFutureio.netty.channelsync()阻塞等待完成,禁止在 EventLoop 线程调用2.2
ChannelFutureio.netty.channeladdListener(GenericFutureListener<...> l)异步回调,EventLoop 线程里的正确做法2.2
ChannelFutureio.netty.channelisSuccess() / cause()判断结果与失败原因2.2
ChannelFutureListenerio.netty.channelCLOSE / CLOSE_ON_FAILURE写失败即关闭连接的预置监听器8.3
ChannelOptionio.netty.channelSO_BACKLOG监听队列长度,服务端 option2.3
ChannelOptionio.netty.channelSO_KEEPALIVETCP keepalive 开关,不等于应用层心跳2.3
ChannelOptionio.netty.channelTCP_NODELAY关闭 Nagle,降低小包延迟2.3
ChannelOptionio.netty.channelSO_REUSEADDR重启时复用地址2.3
ChannelOptionio.netty.channelSO_RCVBUF / SO_SNDBUF内核接收/发送缓冲,改动要看内核上限2.3
ChannelOptionio.netty.channelWRITE_BUFFER_WATER_MARK设置 Netty 写缓冲高低水位9.1
ChannelOptionio.netty.channelAUTO_READ是否自动读,读背压的总开关9.3
ChannelOptionio.netty.channelALLOCATOR指定 ByteBufAllocator5.3
ChannelOptionio.netty.channelCONNECT_TIMEOUT_MILLIS客户端连接超时,毫秒8.1
ChannelConfigio.netty.channelsetAutoRead(boolean) / isAutoRead()运行期开关自动读9.3
ChannelConfigio.netty.channelsetWriteBufferWaterMark(WriteBufferWaterMark)运行期调整水位9.1
ChannelConfigio.netty.channelgetWriteBufferWaterMark()读当前水位,排查参数是否生效9.1

SO_RCVBUF 与 WRITE_BUFFER_WATER_MARK 经常被混为一谈。前者是内核 socket 缓冲区,后者是 Netty 用户态写队列的阈值,Channel.isWritable() 反映的是后者。调小后者不会让内核少收数据,只会更早告诉业务层「别再写了」。

EventLoop 与线程

EventLoop 是单线程执行器,一个 Channel 注册后终身绑定同一个 EventLoop。NioEventLoopGroup(int nThreads) 的 nThreads 是组内 EventLoop 数,不是「每个连接一个线程」;传 0 或不传时走默认值,默认值受 -Dio.netty.eventLoopThreads 影响。

类包成员用途章节
NioEventLoopGroupio.netty.channel.nioNioEventLoopGroup(int nThreads)创建固定线程数的 NIO 组3.1
NioEventLoopGroupio.netty.channel.nioNioEventLoopGroup()使用默认线程数,2 × CPU 核数或系统属性覆盖3.1
EventLoopGroupio.netty.channelnext()取下一个 EventLoop,注册 Channel 时用3.1
EventLoopGroupio.netty.channelregister(Channel channel)把 Channel 注册到组内某个 EventLoop3.1
EventLoopio.netty.channelexecute(Runnable task)跨线程投递任务,替代直接操作 Channel3.2
EventLoopio.netty.channelschedule(Runnable cmd, long delay, TimeUnit unit)延时任务,心跳与超时常用3.2
EventLoopio.netty.channelscheduleAtFixedRate(Runnable cmd, long initial, long period, TimeUnit unit)周期任务,注意任务本身不能阻塞3.2
EventLoopGroupio.netty.channelshutdownGracefully()优雅关闭,先停止接收任务再终止3.1
EventLoopGroupio.netty.channelshutdownGracefully(long quiet, long timeout, TimeUnit unit)指定静默期与总超时3.1
EventLoopGroupio.netty.channelterminationFuture()等待组真正终止3.1
EventLoopGroupio.netty.channelisShuttingDown()判断是否进入关闭流程3.1
DefaultEventExecutorGroupio.netty.util.concurrentDefaultEventExecutorGroup(int nThreads)把阻塞 handler 移出 IO 线程3.3
Epollio.netty.channel.epollisAvailable()Linux 上判断 epoll 构件是否可用12.3
EpollEventLoopGroupio.netty.channel.epollEpollEventLoopGroup(int nThreads)Linux 原生传输的 EventLoop 组12.3
KQueueio.netty.channel.kqueueisAvailable()macOS/BSD 上判断 kqueue 构件是否可用12.2
KQueueEventLoopGroupio.netty.channel.kqueueKQueueEventLoopGroup(int nThreads)macOS 原生传输的 EventLoop 组12.2

在 macOS 上 Epoll.isAvailable() 返回 false 是正常结果,不是依赖损坏。反过来,KQueue.isAvailable() 返回 false 时通常意味着 osx-aarch_64 或 osx-x86_64 的 classifier 没被带进来,代码要回退 NioEventLoopGroup。

Pipeline 与 Handler

Pipeline 的入站事件从 head 到 tail,出站事件从 tail 到 head。ctx.writeAndFlush() 从当前 handler 向前找出站 handler,ctx.channel().writeAndFlush() 从 tail 开始,两者经过的 handler 可能不同。SimpleChannelInboundHandler 默认自动释放入站消息,ByteToMessageDecoder 不释放入参。

类包成员用途章节
ChannelPipelineio.netty.channeladdLast(String name, ChannelHandler h)按名字追加 handler,便于 get/remove4.1
ChannelPipelineio.netty.channeladdLast(ChannelHandler... handlers)一次追加多个,顺序即执行顺序4.1
ChannelPipelineio.netty.channeladdFirst(String name, ChannelHandler h)插到最前,常用于前置日志或限流4.1
ChannelPipelineio.netty.channelremove(ChannelHandler h)动态摘除,如握手完成后移除认证 handler4.2
ChannelPipelineio.netty.channelget(String name)按名取 handler4.1
ChannelPipelineio.netty.channelcontext(ChannelHandler h)取 handler 对应的 ChannelHandlerContext4.1
ChannelPipelineio.netty.channelfireChannelRead(Object msg)从 head 重新触发入站读事件4.1
ChannelPipelineio.netty.channelfireChannelReadComplete()触发一次读完成事件4.2
ChannelPipelineio.netty.channelfireExceptionCaught(Throwable cause)从 head 传播异常6.3
ChannelPipelineio.netty.channelfireUserEventTriggered(Object evt)传播自定义事件,如 IdleStateEvent7.2
ChannelPipelineio.netty.channelwriteAndFlush(Object msg)从 tail 开始走出站4.1
ChannelPipelineio.netty.channelclose()从 tail 开始传播关闭8.3
ChannelHandlerContextio.netty.channelchannel() / pipeline() / name()取所属 Channel、管道与 handler 名4.1
ChannelHandlerContextio.netty.channelwrite(Object msg) / writeAndFlush(Object msg)从当前 handler 向前找出站4.1
ChannelHandlerContextio.netty.channelfireChannelRead(Object msg)把消息交给下一个入站 handler4.1
ChannelHandlerContextio.netty.channelfireExceptionCaught(Throwable cause)把异常交给下一个入站 handler6.3
ChannelHandlerContextio.netty.channelalloc()用当前 Channel 的分配器创建缓冲区5.3
ChannelHandlerContextio.netty.channelclose()关闭所属 Channel8.3
ChannelInitializerio.netty.channelinitChannel(C ch)每条新连接初始化 pipeline 的入口2.1
ChannelInboundHandlerAdapterio.netty.channelchannelRead(ChannelHandlerContext ctx, Object msg)不自动释放的入站基类4.2
ChannelInboundHandlerAdapterio.netty.channelchannelActive(ChannelHandlerContext ctx) / channelInactive(...)连接建立与断开回调4.2
SimpleChannelInboundHandlerio.netty.channelchannelRead0(ChannelHandlerContext ctx, I msg)自动释放入站消息的业务入口4.2
SimpleChannelInboundHandlerio.netty.channelexceptionCaught(ChannelHandlerContext ctx, Throwable cause)捕获本 handler 及下游异常6.3
SimpleChannelInboundHandlerio.netty.channeluserEventTriggered(ChannelHandlerContext ctx, Object evt)处理 IdleStateEvent 等用户事件7.2
ChannelHandlerio.netty.channel@ChannelHandler.Sharable标记无状态 handler 可复用4.3
ChannelDuplexHandlerio.netty.channelchannelRead(...) / write(...)同时处理入站与出站的基类4.2

@Sharable 只适合没有可变成员状态的 handler。WebSocketHub 持有 ChannelGroup,这个成员本身是线程安全的,但如果在 handler 里又加了 int count 或 Map<Channel, ...> 这样的可变字段,就不能标 @Sharable,否则多个连接会同时改同一份状态。

ByteBuf 与引用计数

ByteBuf 有读索引和写索引,readXxx 移动读索引,getXxx 不移动,writeXxx 移动写索引,setXxx 不移动。slice/duplicate 共享底层内存但各自维护索引;带 retained 前缀的重载会增加引用计数,调用方负责释放。

类包成员用途章节
ByteBufAllocatorio.netty.bufferbuffer() / buffer(int initialCapacity)按默认倾向分配(通常 direct)5.3
ByteBufAllocatorio.netty.bufferdirectBuffer(int initialCapacity)明确分配堆外缓冲区5.3
ByteBufAllocatorio.netty.bufferheapBuffer(int initialCapacity)明确分配堆内缓冲区5.1
ByteBufAllocatorio.netty.buffercompositeBuffer()创建 CompositeByteBuf,零拷贝拼接5.1
ByteBufio.netty.bufferreadableBytes() / writableBytes()可读、可写字节数5.1
ByteBufio.netty.buffercapacity() / maxCapacity()当前容量与最大可扩容容量5.1
ByteBufio.netty.bufferreaderIndex() / writerIndex()读、写游标5.1
ByteBufio.netty.bufferreaderIndex(int) / writerIndex(int)直接设置游标,越界抛 IndexOutOfBoundsException5.1
ByteBufio.netty.bufferreadByte() / readInt() / readLong()读取并前移读游标5.1
ByteBufio.netty.bufferreadBytes(byte[] dst)批量读入字节数组6.1
ByteBufio.netty.bufferreadSlice(int length)不增加引用计数的切片,随源释放失效5.1
ByteBufio.netty.bufferreadRetainedSlice(int length)增加引用计数的切片,调用方负责释放5.2
ByteBufio.netty.bufferslice(int index, int length) / retainedSlice(...)指定位置切片,后者带引用计数5.1
ByteBufio.netty.bufferduplicate() / retainedDuplicate()复制索引与引用,共享内存5.2
ByteBufio.netty.buffergetInt(int index) / setInt(int index, int value)绝对读写,不移动游标5.1
ByteBufio.netty.bufferwriteInt(int value) / writeBytes(byte[] src)写入并前移写游标5.1
ByteBufio.netty.bufferdiscardReadBytes() / clear()丢弃已读区、重置双游标5.1
ByteBufio.netty.buffernioBuffer()转成 ByteBuffer 视图,供 JDK API 使用5.1
ByteBufio.netty.bufferrefCnt() / retain() / release()引用计数三件套5.2
ByteBufio.netty.buffertoString(Charset charset)按字符集解码可读区,调试用5.1
Unpooledio.netty.bufferwrappedBuffer(byte[] array)包装现有数组,不拷贝5.1
Unpooledio.netty.buffercopiedBuffer(CharSequence s, Charset c)拷贝字符串内容,测试常用5.1
Unpooledio.netty.bufferbuffer(int) / directBuffer(int)非池化分配,便于对比5.3
Unpooledio.netty.bufferEMPTY_BUFFER空缓冲区常量,只读5.1
PooledByteBufAllocatorio.netty.bufferDEFAULT默认池化分配器实例5.3
PooledByteBufAllocatorio.netty.buffermetric()取 arena 指标,看堆外内存与命中率13.2
CompositeByteBufio.netty.bufferaddComponent(boolean increaseWriterIndex, ByteBuf buffer)追加一个组件,避免拼接拷贝5.1
WriteBufferWaterMarkio.netty.channelWriteBufferWaterMark(int low, int high)构造写缓冲高低水位9.1
ReferenceCountUtilio.netty.utilrelease(Object msg) / retain(Object msg)空安全、类型安全的引用计数操作5.2
ResourceLeakDetectorio.netty.utilsetLevel(ResourceLeakDetector.Level level)运行期调整泄漏检测级别13.1
ResourceLeakDetector.Levelio.netty.utilSIMPLE / ADVANCED / PARANOID三档检测级别,开销依次增大13.1
AttributeKeyio.netty.utilvalueOf(String name)创建 Channel 属性键7.3
Channelio.netty.channelattr(AttributeKey<T> key)在 Channel 上挂会话状态7.3
CharsetUtilio.netty.utilUTF_8避免每次 Charset.forName5.1

readSlice 与 readRetainedSlice 的差别不是「省不省拷贝」,而是「引用计数加不加」。前者返回的切片在源缓冲区 release 后立刻失效,后者让源计数加 1,必须自己 release。ByteToMessageDecoder.decode 里的 in 由父类管理,不能 release;用 readRetainedSlice 取出的切片才归当前方法。

编解码器

解码器分两类:ByteToMessageDecoder 处理「字节到消息」,MessageToMessageDecoder 处理「消息到消息」。编码器对应 MessageToByteEncoder 与 MessageToMessageEncoder。长度域解码器是 ByteToMessageDecoder 的现成实现,参数顺序错一位就会得到「永远等不够」或「每帧多读几个字节」。

类包成员用途章节
ByteToMessageDecoderio.netty.handler.codecdecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)从累积缓冲区拆出一条或多条消息6.1
MessageToByteEncoderio.netty.handler.codecencode(ChannelHandlerContext ctx, I msg, ByteBuf out)把业务对象写成字节6.1
MessageToMessageDecoderio.netty.handler.codecdecode(ChannelHandlerContext ctx, I msg, List<Object> out)消息类型转换,如帧转请求对象6.1
MessageToMessageEncoderio.netty.handler.codecencode(ChannelHandlerContext ctx, I msg, List<Object> out)出站消息类型转换6.1
MessageToMessageCodecio.netty.handler.codecdecode(...) / encode(...)双向转换合在一个类里6.1
LengthFieldBasedFrameDecoderio.netty.handler.codec(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength)按长度字段拆帧,最简三参形式6.2
LengthFieldBasedFrameDecoderio.netty.handler.codec(int max, int offset, int length, int adjustment, int strip)带长度修正与头部剥离的完整形式6.2
LengthFieldPrependerio.netty.handler.codecLengthFieldPrepender(int lengthFieldLength)出站时在负载前补长度字段6.2
LengthFieldPrependerio.netty.handler.codecLengthFieldPrepender(int length, boolean includesLength)长度是否包含长度字段自身6.2
DelimiterBasedFrameDecoderio.netty.handler.codec(int maxFrameLength, ByteBuf delimiter)按分隔符拆帧,文本协议常用6.2
FixedLengthFrameDecoderio.netty.handler.codecFixedLengthFrameDecoder(int frameLength)定长拆帧6.2
TooLongFrameExceptionio.netty.handler.codecTooLongFrameException(String message)超长帧时抛出,交给异常处理器6.3
ChunkedWriteHandlerio.netty.handler.streamChunkedWriteHandler()分块写大文件,避免一次性占满内存8.3
ChunkedFileio.netty.handler.streamChunkedFile(File file)以块为单位读取文件8.3

LengthFieldBasedFrameDecoder 的 lengthAdjustment 是「长度字段的值 + adjustment = 后续负载长度」。如果长度字段只记录负载、不包含头部,通常 lengthAdjustment = 0;如果长度字段记录了「头部 + 负载」,要减掉头部长度。initialBytesToStrip 决定交给下游的帧是否还带长度头。

HTTP/1.1

HTTP 的 pipeline 通常由三部分组成:HttpServerCodec 做请求/响应编解码,HttpObjectAggregator 把分块的 HttpContent 聚合成 FullHttpRequest,业务 handler 处理完整请求。聚合有内存代价,maxContentLength 必须显式设置。

类包成员用途章节
HttpServerCodecio.netty.handler.codec.httpHttpServerCodec()服务端请求解码 + 响应编码15.1
HttpObjectAggregatorio.netty.handler.codec.httpHttpObjectAggregator(int maxContentLength)聚合分块请求,超限抛 TooLongFrameException15.1
HttpClientCodecio.netty.handler.codec.httpHttpClientCodec()客户端请求编码 + 响应解码15.1
FullHttpRequestio.netty.handler.codec.httpmethod() / uri() / headers() / content()取方法、路径、头与 body15.1
DefaultFullHttpResponseio.netty.handler.codec.http(HttpVersion v, HttpResponseStatus s, ByteBuf content)构造带 body 的完整响应15.1
FullHttpResponseio.netty.handler.codec.httpstatus()读响应状态15.1
HttpResponseStatusio.netty.handler.codec.httpOK / BAD_REQUEST / NOT_FOUND常用状态常量15.1
HttpResponseStatusio.netty.handler.codec.httpBAD_GATEWAY / INTERNAL_SERVER_ERROR代理失败与服务端错误15.1
HttpHeaderNamesio.netty.handler.codec.httpCONTENT_TYPE / CONTENT_LENGTH / CONNECTION常用头名常量,避免拼错大小写15.1
HttpUtilio.netty.handler.codec.httpisKeepAlive(req) / setKeepAlive(resp, boolean)读写 keep-alive 语义15.1
HttpUtilio.netty.handler.codec.httpsetContentLength(resp, long)设置 Content-Length15.1
HttpMethodio.netty.handler.codec.httpGET / POST方法常量,比较用 equals15.1
HttpVersionio.netty.handler.codec.httpHTTP_1_1构造响应时指定协议版本15.1
QueryStringDecoderio.netty.handler.codec.httppath() / parameters()拆路径与查询参数,自动解码15.2

FullHttpRequest 的 content() 是 ByteBuf,它的生命周期跟随请求对象。LinkdHttpHandler 继承 SimpleChannelInboundHandler<FullHttpRequest> 时,channelRead0 返回后请求会被释放,不能把 req 或 req.content() 存进会话表;要异步处理就先 retain(),任务结束再 release()。

WebSocket

WebSocket 服务端由 WebSocketServerProtocolHandler 完成握手与协议升级,业务 handler 只处理 TextWebSocketFrame、BinaryWebSocketFrame 等帧。握手完成会触发 ServerHandshakeStateEvent.HANDSHAKE_COMPLETE,会话表通常在这一刻登记连接。

类包成员用途章节
WebSocketServerProtocolHandlerio.netty.handler.codec.http.websocketx(String websocketPath)指定升级路径,如 /ws/echo10.1
WebSocketServerProtocolHandlerio.netty.handler.codec.http.websocketx(String path, String subprotocols)同时声明子协议10.1
WebSocketFrameAggregatorio.netty.handler.codec.http.websocketxWebSocketFrameAggregator(int maxContentLength)聚合分片帧,超限拒绝10.2
TextWebSocketFrameio.netty.handler.codec.http.websocketxTextWebSocketFrame(String text) / text()文本帧构造与读取10.2
BinaryWebSocketFrameio.netty.handler.codec.http.websocketxBinaryWebSocketFrame(ByteBuf data) / content()二进制帧构造与读取10.2
PingWebSocketFrameio.netty.handler.codec.http.websocketxPingWebSocketFrame()构造 ping 帧,对端应回 pong10.3
PongWebSocketFrameio.netty.handler.codec.http.websocketxPongWebSocketFrame()回应 ping10.3
CloseWebSocketFrameio.netty.handler.codec.http.websocketx(int statusCode, String reasonText)带状态码与原因的关闭帧10.3
ServerHandshakeStateEventio.netty.handler.codec.http.websocketxHANDSHAKE_COMPLETE握手完成事件,登记会话的时机10.3

文本帧的 text() 返回 String,每次调用都可能重新解码;广播时不要对同一帧反复取 text() 再重建,直接复用 TextWebSocketFrame 并配合 retainedDuplicate() 更稳妥。WebSocketHub 持有 ChannelGroup,关闭连接时要从组里移除,否则广播会一直命中已关闭的 Channel。

HTTP/2

HTTP/2 分两层:Http2FrameCodec 处理连接级帧,Http2MultiplexHandler 把每个 stream 变成子 Channel。业务代码通常挂在子 Channel 的 pipeline 上,用 Http2StreamFrameToHttpObjectCodec 把 Http2HeadersFrame/Http2DataFrame 转成 HttpRequest/HttpContent。

类包成员用途章节
Http2FrameCodecBuilderio.netty.handler.codec.http2forServer() / build()构造服务端连接级帧编解码器11.1
Http2MultiplexHandlerio.netty.handler.codec.http2Http2MultiplexHandler(ChannelHandler inboundStreamHandler)为每个 stream 创建子 Channel11.1
Http2StreamFrameToHttpObjectCodecio.netty.handler.codec.http2Http2StreamFrameToHttpObjectCodec(boolean isServer)帧与 HTTP 对象互转11.2
Http2HeadersFrameio.netty.handler.codec.http2headers() / method() / path()读取 HEADERS 帧的伪头11.1
Http2HeadersFrameio.netty.handler.codec.http2stream()取所属 Http2FrameStream11.1
Http2DataFrameio.netty.handler.codec.http2content() / endStream()读数据帧与流结束标志11.2
Http2SettingsFrameio.netty.handler.codec.http2Http2SettingsFrame 类型判断处理连接级 SETTINGS11.2
Http2GoAwayFrameio.netty.handler.codec.http2errorCode()读 GOAWAY 错误码,判断对端为何关闭11.2
Http2FrameStreamio.netty.handler.codec.http2id()stream 标识,日志关联请求11.1
Http2StreamChannelBootstrapio.netty.handler.codec.http2(Channel ch) / handler(ChannelHandler h) / open()在连接上主动开子 Channel11.2

不要直接把 Http2HeadersFrame 当 FullHttpRequest 用。前者只有头,body 在后续 Http2DataFrame 里;需要完整请求时要么自己聚合,要么挂 Http2StreamFrameToHttpObjectCodec 再配 HttpObjectAggregator。

连接池

连接池的语义是「借出、归还」,不是「缓存 Channel 对象」。FixedChannelPool 限制最大连接数,SimpleChannelPool 不限制;ChannelPoolHandler 在创建、借出、归还三个时点回调,健康检查决定归还的连接还能不能用。

类包成员用途章节
FixedChannelPoolio.netty.channel.pool(Bootstrap b, ChannelPoolHandler h, int maxConnections)有上限的连接池8.2
SimpleChannelPoolio.netty.channel.pool(Bootstrap b, ChannelPoolHandler h)无上限的简单池8.2
ChannelPoolio.netty.channel.poolacquire()借出一个 Channel,返回 Future<Channel>8.2
ChannelPoolio.netty.channel.poolrelease(Channel ch)归还连接8.2
ChannelPoolio.netty.channel.poolclose()关闭池并释放所有空闲连接8.2
ChannelPoolHandlerio.netty.channel.poolchannelCreated(Channel ch)新连接创建时装配 pipeline8.2
ChannelPoolHandlerio.netty.channel.poolchannelAcquired(Channel ch) / channelReleased(Channel ch)借出、归还时重置状态8.2
ChannelPoolMapio.netty.channel.poolget(SocketAddress key)按远端地址取池,多上游场景8.2
AbstractChannelPoolMapio.netty.channel.poolnewPool(SocketAddress key)实现按 key 建池的模板方法8.2
ChannelHealthCheckerio.netty.channel.poolACTIVE归还时检查 Channel.isActive()8.2

ChannelPool.release() 返回的 future 完成前,这条连接不能再次借出。异常路径上忘记 release,池子的可用连接会越来越少,表现为 acquire() 一直等不到结果;这类问题看 FixedChannelPool 的 pending 数量比看 GC 更直接。

签名与常量速记

下面这些差异在正文里分散出现过,集中列一次:

容易记错的点正确写法错误写法
服务端连接参数childOption(ChannelOption.TCP_NODELAY, true)option(ChannelOption.TCP_NODELAY, true)
水位线参数类型new WriteBufferWaterMark(32 * 1024, 64 * 1024)new WriteBufferWaterMark(32, 64) 当字节数
自动释放的 handlerSimpleChannelInboundHandler<I>SimpleChannelInboundHandler 里再 release 一次
解码器入参decode(ctx, in, out) 只移动读游标in.release()
切片所有权readRetainedSlice(n) 由调用方 release把 readSlice(n) 存进异步任务
广播同一帧每条连接 retainedDuplicate()同一个 ByteBuf 写给 N 条连接
关闭监听ChannelFutureListener.CLOSE自己写匿名类重复实现

方法签名以 Netty 4.2.19.Final 的 javadoc 为准;本表省略了返回值与 throws,是为了在纸面上可读。真正拿不准时,用 IDE 跳转到 netty-transport-4.2.19.Final.jar 里的类,比搜索引擎可靠。

小结

  • 先记包名:ChannelGroup 在 io.netty.channel.group,NioEventLoopGroup 在 io.netty.channel.nio,HTTP/2 全部在 io.netty.handler.codec.http2。
  • 服务端用 ServerBootstrap 的 childHandler/childOption,客户端用 Bootstrap 的 handler/option;两组方法不要互相替换。
  • ChannelFuture.sync() 只留给启动线程和测试;EventLoop 线程里用 addListener。
  • Pipeline 的入站从 head 到 tail、出站从 tail 到 head;ctx.writeAndFlush 与 channel().writeAndFlush 起点不同。
  • ByteBuf 的 readXxx/writeXxx 移动游标,getXxx/setXxx 不移动;带 retained 的切片归调用方释放。
  • SimpleChannelInboundHandler 自动释放,ByteToMessageDecoder 不释放入参;这两条决定了大部分引用计数错误。
  • HTTP 先聚合再处理,WebSocket 先握手再登记会话,HTTP/2 先分子 Channel 再谈请求对象。
  • 连接池的核心是借还配对,异常路径不归还,池子会静默枯竭。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Netty 高性能网络编程》15.3 linkd-v2 验收与复盘
  2. 《Netty 高性能网络编程》15.2 限流、超时与降级
  3. 《Netty 高性能网络编程》15.1 网关路由与转发