这张表怎么用
这不是教程,是把正文里出现过的类、包名和方法签名压成一张可查的表。写 linkd-v2 时先在这里定位类,再回到对应章节看为什么这样用。
约定如下:
- 版本固定为 Netty 4.2.19.Final,JDK 25。方法签名省略返回值类型,只保留参数,避免表格过宽;需要返回值时看「用途」一列。
- 「包」一列是完整包名。Netty 的类分散在
netty-transport、netty-buffer、netty-codec-http等模块里,netty-all只是聚合 POM,import 写错包是编译期最常见的错误。 - 「章节」指向正文的节号,附录不重复解释机制。
- 表格里的
...表示可变参数或省略的重载,不是可以照抄的省略号。
包名先单独记四个容易混的:
| 类 | 正确包 | 常见错写 |
|---|---|---|
ChannelGroup | io.netty.channel.group | io.netty.channel |
NioEventLoopGroup | io.netty.channel.nio | io.netty.channel |
Epoll / KQueue | io.netty.channel.epoll / io.netty.channel.kqueue | io.netty.channel.socket |
Http2FrameCodecBuilder | io.netty.handler.codec.http2 | io.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()。
| 类 | 包 | 成员 | 用途 | 章节 |
|---|---|---|---|---|
ServerBootstrap | io.netty.bootstrap | group(EventLoopGroup parent, EventLoopGroup child) | 设置接受连接的 boss 组与处理 IO 的 worker 组 | 2.1 |
ServerBootstrap | io.netty.bootstrap | channel(Class<? extends ServerChannel> cls) | 指定服务端 Channel 实现,如 NioServerSocketChannel | 2.1 |
ServerBootstrap | io.netty.bootstrap | childHandler(ChannelHandler handler) | 每条新连接初始化时调用的 handler | 2.1 |
ServerBootstrap | io.netty.bootstrap | option(ChannelOption<T> opt, T value) | 作用于监听 socket 的参数,如 SO_BACKLOG | 2.3 |
ServerBootstrap | io.netty.bootstrap | childOption(ChannelOption<T> opt, T value) | 作用于每条已接受连接的参数,如 TCP_NODELAY | 2.3 |
ServerBootstrap | io.netty.bootstrap | bind(int port) / bind(SocketAddress local) | 绑定端口,返回 ChannelFuture | 2.1 |
Bootstrap | io.netty.bootstrap | group(EventLoopGroup group) | 客户端只设一组 EventLoop | 8.1 |
Bootstrap | io.netty.bootstrap | channel(Class<? extends Channel> cls) | 指定客户端 Channel,如 NioSocketChannel | 8.1 |
Bootstrap | io.netty.bootstrap | handler(ChannelHandler handler) | 客户端 pipeline 的初始化 handler | 8.1 |
Bootstrap | io.netty.bootstrap | connect(String host, int port) / connect(SocketAddress remote) | 发起连接,返回 ChannelFuture | 8.1 |
Bootstrap | io.netty.bootstrap | remoteAddress(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) 编译不过。
| 类 | 包 | 成员 | 用途 | 章节 |
|---|---|---|---|---|
Channel | io.netty.channel | id() | 连接的唯一标识,日志里用它关联一次会话 | 2.2 |
Channel | io.netty.channel | isActive() / isOpen() | 是否已连接、是否尚未关闭 | 2.2 |
Channel | io.netty.channel | isWritable() | 写缓冲是否低于高水位,背压判断入口 | 9.1 |
Channel | io.netty.channel | config() | 取 ChannelConfig,运行期改 AUTO_READ 等 | 9.3 |
Channel | io.netty.channel | pipeline() | 取管道,动态增删 handler | 4.1 |
Channel | io.netty.channel | eventLoop() | 取绑定的 EventLoop,用于判断当前线程 | 3.1 |
Channel | io.netty.channel | alloc() | 取该 Channel 的 ByteBufAllocator | 5.3 |
Channel | io.netty.channel | read() | 在 AUTO_READ=false 时主动请求一次读 | 9.3 |
Channel | io.netty.channel | write(Object msg) | 写到 pipeline,尚未 flush | 9.2 |
Channel | io.netty.channel | writeAndFlush(Object msg) | 写并 flush,返回 ChannelFuture | 9.2 |
Channel | io.netty.channel | close() / closeFuture() | 主动关闭、监听关闭完成 | 8.3 |
Channel | io.netty.channel | parent() | 子 Channel 取父 Channel,HTTP/2 stream 常用 | 11.1 |
Channel | io.netty.channel | remoteAddress() / localAddress() | 对端与本地地址,日志与连接表使用 | 2.2 |
ChannelFuture | io.netty.channel | channel() | 从 future 取回 Channel | 2.2 |
ChannelFuture | io.netty.channel | sync() | 阻塞等待完成,禁止在 EventLoop 线程调用 | 2.2 |
ChannelFuture | io.netty.channel | addListener(GenericFutureListener<...> l) | 异步回调,EventLoop 线程里的正确做法 | 2.2 |
ChannelFuture | io.netty.channel | isSuccess() / cause() | 判断结果与失败原因 | 2.2 |
ChannelFutureListener | io.netty.channel | CLOSE / CLOSE_ON_FAILURE | 写失败即关闭连接的预置监听器 | 8.3 |
ChannelOption | io.netty.channel | SO_BACKLOG | 监听队列长度,服务端 option | 2.3 |
ChannelOption | io.netty.channel | SO_KEEPALIVE | TCP keepalive 开关,不等于应用层心跳 | 2.3 |
ChannelOption | io.netty.channel | TCP_NODELAY | 关闭 Nagle,降低小包延迟 | 2.3 |
ChannelOption | io.netty.channel | SO_REUSEADDR | 重启时复用地址 | 2.3 |
ChannelOption | io.netty.channel | SO_RCVBUF / SO_SNDBUF | 内核接收/发送缓冲,改动要看内核上限 | 2.3 |
ChannelOption | io.netty.channel | WRITE_BUFFER_WATER_MARK | 设置 Netty 写缓冲高低水位 | 9.1 |
ChannelOption | io.netty.channel | AUTO_READ | 是否自动读,读背压的总开关 | 9.3 |
ChannelOption | io.netty.channel | ALLOCATOR | 指定 ByteBufAllocator | 5.3 |
ChannelOption | io.netty.channel | CONNECT_TIMEOUT_MILLIS | 客户端连接超时,毫秒 | 8.1 |
ChannelConfig | io.netty.channel | setAutoRead(boolean) / isAutoRead() | 运行期开关自动读 | 9.3 |
ChannelConfig | io.netty.channel | setWriteBufferWaterMark(WriteBufferWaterMark) | 运行期调整水位 | 9.1 |
ChannelConfig | io.netty.channel | getWriteBufferWaterMark() | 读当前水位,排查参数是否生效 | 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 影响。
| 类 | 包 | 成员 | 用途 | 章节 |
|---|---|---|---|---|
NioEventLoopGroup | io.netty.channel.nio | NioEventLoopGroup(int nThreads) | 创建固定线程数的 NIO 组 | 3.1 |
NioEventLoopGroup | io.netty.channel.nio | NioEventLoopGroup() | 使用默认线程数,2 × CPU 核数或系统属性覆盖 | 3.1 |
EventLoopGroup | io.netty.channel | next() | 取下一个 EventLoop,注册 Channel 时用 | 3.1 |
EventLoopGroup | io.netty.channel | register(Channel channel) | 把 Channel 注册到组内某个 EventLoop | 3.1 |
EventLoop | io.netty.channel | execute(Runnable task) | 跨线程投递任务,替代直接操作 Channel | 3.2 |
EventLoop | io.netty.channel | schedule(Runnable cmd, long delay, TimeUnit unit) | 延时任务,心跳与超时常用 | 3.2 |
EventLoop | io.netty.channel | scheduleAtFixedRate(Runnable cmd, long initial, long period, TimeUnit unit) | 周期任务,注意任务本身不能阻塞 | 3.2 |
EventLoopGroup | io.netty.channel | shutdownGracefully() | 优雅关闭,先停止接收任务再终止 | 3.1 |
EventLoopGroup | io.netty.channel | shutdownGracefully(long quiet, long timeout, TimeUnit unit) | 指定静默期与总超时 | 3.1 |
EventLoopGroup | io.netty.channel | terminationFuture() | 等待组真正终止 | 3.1 |
EventLoopGroup | io.netty.channel | isShuttingDown() | 判断是否进入关闭流程 | 3.1 |
DefaultEventExecutorGroup | io.netty.util.concurrent | DefaultEventExecutorGroup(int nThreads) | 把阻塞 handler 移出 IO 线程 | 3.3 |
Epoll | io.netty.channel.epoll | isAvailable() | Linux 上判断 epoll 构件是否可用 | 12.3 |
EpollEventLoopGroup | io.netty.channel.epoll | EpollEventLoopGroup(int nThreads) | Linux 原生传输的 EventLoop 组 | 12.3 |
KQueue | io.netty.channel.kqueue | isAvailable() | macOS/BSD 上判断 kqueue 构件是否可用 | 12.2 |
KQueueEventLoopGroup | io.netty.channel.kqueue | KQueueEventLoopGroup(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 不释放入参。
| 类 | 包 | 成员 | 用途 | 章节 |
|---|---|---|---|---|
ChannelPipeline | io.netty.channel | addLast(String name, ChannelHandler h) | 按名字追加 handler,便于 get/remove | 4.1 |
ChannelPipeline | io.netty.channel | addLast(ChannelHandler... handlers) | 一次追加多个,顺序即执行顺序 | 4.1 |
ChannelPipeline | io.netty.channel | addFirst(String name, ChannelHandler h) | 插到最前,常用于前置日志或限流 | 4.1 |
ChannelPipeline | io.netty.channel | remove(ChannelHandler h) | 动态摘除,如握手完成后移除认证 handler | 4.2 |
ChannelPipeline | io.netty.channel | get(String name) | 按名取 handler | 4.1 |
ChannelPipeline | io.netty.channel | context(ChannelHandler h) | 取 handler 对应的 ChannelHandlerContext | 4.1 |
ChannelPipeline | io.netty.channel | fireChannelRead(Object msg) | 从 head 重新触发入站读事件 | 4.1 |
ChannelPipeline | io.netty.channel | fireChannelReadComplete() | 触发一次读完成事件 | 4.2 |
ChannelPipeline | io.netty.channel | fireExceptionCaught(Throwable cause) | 从 head 传播异常 | 6.3 |
ChannelPipeline | io.netty.channel | fireUserEventTriggered(Object evt) | 传播自定义事件,如 IdleStateEvent | 7.2 |
ChannelPipeline | io.netty.channel | writeAndFlush(Object msg) | 从 tail 开始走出站 | 4.1 |
ChannelPipeline | io.netty.channel | close() | 从 tail 开始传播关闭 | 8.3 |
ChannelHandlerContext | io.netty.channel | channel() / pipeline() / name() | 取所属 Channel、管道与 handler 名 | 4.1 |
ChannelHandlerContext | io.netty.channel | write(Object msg) / writeAndFlush(Object msg) | 从当前 handler 向前找出站 | 4.1 |
ChannelHandlerContext | io.netty.channel | fireChannelRead(Object msg) | 把消息交给下一个入站 handler | 4.1 |
ChannelHandlerContext | io.netty.channel | fireExceptionCaught(Throwable cause) | 把异常交给下一个入站 handler | 6.3 |
ChannelHandlerContext | io.netty.channel | alloc() | 用当前 Channel 的分配器创建缓冲区 | 5.3 |
ChannelHandlerContext | io.netty.channel | close() | 关闭所属 Channel | 8.3 |
ChannelInitializer | io.netty.channel | initChannel(C ch) | 每条新连接初始化 pipeline 的入口 | 2.1 |
ChannelInboundHandlerAdapter | io.netty.channel | channelRead(ChannelHandlerContext ctx, Object msg) | 不自动释放的入站基类 | 4.2 |
ChannelInboundHandlerAdapter | io.netty.channel | channelActive(ChannelHandlerContext ctx) / channelInactive(...) | 连接建立与断开回调 | 4.2 |
SimpleChannelInboundHandler | io.netty.channel | channelRead0(ChannelHandlerContext ctx, I msg) | 自动释放入站消息的业务入口 | 4.2 |
SimpleChannelInboundHandler | io.netty.channel | exceptionCaught(ChannelHandlerContext ctx, Throwable cause) | 捕获本 handler 及下游异常 | 6.3 |
SimpleChannelInboundHandler | io.netty.channel | userEventTriggered(ChannelHandlerContext ctx, Object evt) | 处理 IdleStateEvent 等用户事件 | 7.2 |
ChannelHandler | io.netty.channel | @ChannelHandler.Sharable | 标记无状态 handler 可复用 | 4.3 |
ChannelDuplexHandler | io.netty.channel | channelRead(...) / write(...) | 同时处理入站与出站的基类 | 4.2 |
@Sharable 只适合没有可变成员状态的 handler。WebSocketHub 持有 ChannelGroup,这个成员本身是线程安全的,但如果在 handler 里又加了 int count 或 Map<Channel, ...> 这样的可变字段,就不能标 @Sharable,否则多个连接会同时改同一份状态。
ByteBuf 与引用计数
ByteBuf 有读索引和写索引,readXxx 移动读索引,getXxx 不移动,writeXxx 移动写索引,setXxx 不移动。slice/duplicate 共享底层内存但各自维护索引;带 retained 前缀的重载会增加引用计数,调用方负责释放。
| 类 | 包 | 成员 | 用途 | 章节 |
|---|---|---|---|---|
ByteBufAllocator | io.netty.buffer | buffer() / buffer(int initialCapacity) | 按默认倾向分配(通常 direct) | 5.3 |
ByteBufAllocator | io.netty.buffer | directBuffer(int initialCapacity) | 明确分配堆外缓冲区 | 5.3 |
ByteBufAllocator | io.netty.buffer | heapBuffer(int initialCapacity) | 明确分配堆内缓冲区 | 5.1 |
ByteBufAllocator | io.netty.buffer | compositeBuffer() | 创建 CompositeByteBuf,零拷贝拼接 | 5.1 |
ByteBuf | io.netty.buffer | readableBytes() / writableBytes() | 可读、可写字节数 | 5.1 |
ByteBuf | io.netty.buffer | capacity() / maxCapacity() | 当前容量与最大可扩容容量 | 5.1 |
ByteBuf | io.netty.buffer | readerIndex() / writerIndex() | 读、写游标 | 5.1 |
ByteBuf | io.netty.buffer | readerIndex(int) / writerIndex(int) | 直接设置游标,越界抛 IndexOutOfBoundsException | 5.1 |
ByteBuf | io.netty.buffer | readByte() / readInt() / readLong() | 读取并前移读游标 | 5.1 |
ByteBuf | io.netty.buffer | readBytes(byte[] dst) | 批量读入字节数组 | 6.1 |
ByteBuf | io.netty.buffer | readSlice(int length) | 不增加引用计数的切片,随源释放失效 | 5.1 |
ByteBuf | io.netty.buffer | readRetainedSlice(int length) | 增加引用计数的切片,调用方负责释放 | 5.2 |
ByteBuf | io.netty.buffer | slice(int index, int length) / retainedSlice(...) | 指定位置切片,后者带引用计数 | 5.1 |
ByteBuf | io.netty.buffer | duplicate() / retainedDuplicate() | 复制索引与引用,共享内存 | 5.2 |
ByteBuf | io.netty.buffer | getInt(int index) / setInt(int index, int value) | 绝对读写,不移动游标 | 5.1 |
ByteBuf | io.netty.buffer | writeInt(int value) / writeBytes(byte[] src) | 写入并前移写游标 | 5.1 |
ByteBuf | io.netty.buffer | discardReadBytes() / clear() | 丢弃已读区、重置双游标 | 5.1 |
ByteBuf | io.netty.buffer | nioBuffer() | 转成 ByteBuffer 视图,供 JDK API 使用 | 5.1 |
ByteBuf | io.netty.buffer | refCnt() / retain() / release() | 引用计数三件套 | 5.2 |
ByteBuf | io.netty.buffer | toString(Charset charset) | 按字符集解码可读区,调试用 | 5.1 |
Unpooled | io.netty.buffer | wrappedBuffer(byte[] array) | 包装现有数组,不拷贝 | 5.1 |
Unpooled | io.netty.buffer | copiedBuffer(CharSequence s, Charset c) | 拷贝字符串内容,测试常用 | 5.1 |
Unpooled | io.netty.buffer | buffer(int) / directBuffer(int) | 非池化分配,便于对比 | 5.3 |
Unpooled | io.netty.buffer | EMPTY_BUFFER | 空缓冲区常量,只读 | 5.1 |
PooledByteBufAllocator | io.netty.buffer | DEFAULT | 默认池化分配器实例 | 5.3 |
PooledByteBufAllocator | io.netty.buffer | metric() | 取 arena 指标,看堆外内存与命中率 | 13.2 |
CompositeByteBuf | io.netty.buffer | addComponent(boolean increaseWriterIndex, ByteBuf buffer) | 追加一个组件,避免拼接拷贝 | 5.1 |
WriteBufferWaterMark | io.netty.channel | WriteBufferWaterMark(int low, int high) | 构造写缓冲高低水位 | 9.1 |
ReferenceCountUtil | io.netty.util | release(Object msg) / retain(Object msg) | 空安全、类型安全的引用计数操作 | 5.2 |
ResourceLeakDetector | io.netty.util | setLevel(ResourceLeakDetector.Level level) | 运行期调整泄漏检测级别 | 13.1 |
ResourceLeakDetector.Level | io.netty.util | SIMPLE / ADVANCED / PARANOID | 三档检测级别,开销依次增大 | 13.1 |
AttributeKey | io.netty.util | valueOf(String name) | 创建 Channel 属性键 | 7.3 |
Channel | io.netty.channel | attr(AttributeKey<T> key) | 在 Channel 上挂会话状态 | 7.3 |
CharsetUtil | io.netty.util | UTF_8 | 避免每次 Charset.forName | 5.1 |
readSlice 与 readRetainedSlice 的差别不是「省不省拷贝」,而是「引用计数加不加」。前者返回的切片在源缓冲区 release 后立刻失效,后者让源计数加 1,必须自己 release。ByteToMessageDecoder.decode 里的 in 由父类管理,不能 release;用 readRetainedSlice 取出的切片才归当前方法。
编解码器
解码器分两类:ByteToMessageDecoder 处理「字节到消息」,MessageToMessageDecoder 处理「消息到消息」。编码器对应 MessageToByteEncoder 与 MessageToMessageEncoder。长度域解码器是 ByteToMessageDecoder 的现成实现,参数顺序错一位就会得到「永远等不够」或「每帧多读几个字节」。
| 类 | 包 | 成员 | 用途 | 章节 |
|---|---|---|---|---|
ByteToMessageDecoder | io.netty.handler.codec | decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) | 从累积缓冲区拆出一条或多条消息 | 6.1 |
MessageToByteEncoder | io.netty.handler.codec | encode(ChannelHandlerContext ctx, I msg, ByteBuf out) | 把业务对象写成字节 | 6.1 |
MessageToMessageDecoder | io.netty.handler.codec | decode(ChannelHandlerContext ctx, I msg, List<Object> out) | 消息类型转换,如帧转请求对象 | 6.1 |
MessageToMessageEncoder | io.netty.handler.codec | encode(ChannelHandlerContext ctx, I msg, List<Object> out) | 出站消息类型转换 | 6.1 |
MessageToMessageCodec | io.netty.handler.codec | decode(...) / encode(...) | 双向转换合在一个类里 | 6.1 |
LengthFieldBasedFrameDecoder | io.netty.handler.codec | (int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) | 按长度字段拆帧,最简三参形式 | 6.2 |
LengthFieldBasedFrameDecoder | io.netty.handler.codec | (int max, int offset, int length, int adjustment, int strip) | 带长度修正与头部剥离的完整形式 | 6.2 |
LengthFieldPrepender | io.netty.handler.codec | LengthFieldPrepender(int lengthFieldLength) | 出站时在负载前补长度字段 | 6.2 |
LengthFieldPrepender | io.netty.handler.codec | LengthFieldPrepender(int length, boolean includesLength) | 长度是否包含长度字段自身 | 6.2 |
DelimiterBasedFrameDecoder | io.netty.handler.codec | (int maxFrameLength, ByteBuf delimiter) | 按分隔符拆帧,文本协议常用 | 6.2 |
FixedLengthFrameDecoder | io.netty.handler.codec | FixedLengthFrameDecoder(int frameLength) | 定长拆帧 | 6.2 |
TooLongFrameException | io.netty.handler.codec | TooLongFrameException(String message) | 超长帧时抛出,交给异常处理器 | 6.3 |
ChunkedWriteHandler | io.netty.handler.stream | ChunkedWriteHandler() | 分块写大文件,避免一次性占满内存 | 8.3 |
ChunkedFile | io.netty.handler.stream | ChunkedFile(File file) | 以块为单位读取文件 | 8.3 |
LengthFieldBasedFrameDecoder 的 lengthAdjustment 是「长度字段的值 + adjustment = 后续负载长度」。如果长度字段只记录负载、不包含头部,通常 lengthAdjustment = 0;如果长度字段记录了「头部 + 负载」,要减掉头部长度。initialBytesToStrip 决定交给下游的帧是否还带长度头。
HTTP/1.1
HTTP 的 pipeline 通常由三部分组成:HttpServerCodec 做请求/响应编解码,HttpObjectAggregator 把分块的 HttpContent 聚合成 FullHttpRequest,业务 handler 处理完整请求。聚合有内存代价,maxContentLength 必须显式设置。
| 类 | 包 | 成员 | 用途 | 章节 |
|---|---|---|---|---|
HttpServerCodec | io.netty.handler.codec.http | HttpServerCodec() | 服务端请求解码 + 响应编码 | 15.1 |
HttpObjectAggregator | io.netty.handler.codec.http | HttpObjectAggregator(int maxContentLength) | 聚合分块请求,超限抛 TooLongFrameException | 15.1 |
HttpClientCodec | io.netty.handler.codec.http | HttpClientCodec() | 客户端请求编码 + 响应解码 | 15.1 |
FullHttpRequest | io.netty.handler.codec.http | method() / uri() / headers() / content() | 取方法、路径、头与 body | 15.1 |
DefaultFullHttpResponse | io.netty.handler.codec.http | (HttpVersion v, HttpResponseStatus s, ByteBuf content) | 构造带 body 的完整响应 | 15.1 |
FullHttpResponse | io.netty.handler.codec.http | status() | 读响应状态 | 15.1 |
HttpResponseStatus | io.netty.handler.codec.http | OK / BAD_REQUEST / NOT_FOUND | 常用状态常量 | 15.1 |
HttpResponseStatus | io.netty.handler.codec.http | BAD_GATEWAY / INTERNAL_SERVER_ERROR | 代理失败与服务端错误 | 15.1 |
HttpHeaderNames | io.netty.handler.codec.http | CONTENT_TYPE / CONTENT_LENGTH / CONNECTION | 常用头名常量,避免拼错大小写 | 15.1 |
HttpUtil | io.netty.handler.codec.http | isKeepAlive(req) / setKeepAlive(resp, boolean) | 读写 keep-alive 语义 | 15.1 |
HttpUtil | io.netty.handler.codec.http | setContentLength(resp, long) | 设置 Content-Length | 15.1 |
HttpMethod | io.netty.handler.codec.http | GET / POST | 方法常量,比较用 equals | 15.1 |
HttpVersion | io.netty.handler.codec.http | HTTP_1_1 | 构造响应时指定协议版本 | 15.1 |
QueryStringDecoder | io.netty.handler.codec.http | path() / parameters() | 拆路径与查询参数,自动解码 | 15.2 |
FullHttpRequest 的 content() 是 ByteBuf,它的生命周期跟随请求对象。LinkdHttpHandler 继承 SimpleChannelInboundHandler<FullHttpRequest> 时,channelRead0 返回后请求会被释放,不能把 req 或 req.content() 存进会话表;要异步处理就先 retain(),任务结束再 release()。
WebSocket
WebSocket 服务端由 WebSocketServerProtocolHandler 完成握手与协议升级,业务 handler 只处理 TextWebSocketFrame、BinaryWebSocketFrame 等帧。握手完成会触发 ServerHandshakeStateEvent.HANDSHAKE_COMPLETE,会话表通常在这一刻登记连接。
| 类 | 包 | 成员 | 用途 | 章节 |
|---|---|---|---|---|
WebSocketServerProtocolHandler | io.netty.handler.codec.http.websocketx | (String websocketPath) | 指定升级路径,如 /ws/echo | 10.1 |
WebSocketServerProtocolHandler | io.netty.handler.codec.http.websocketx | (String path, String subprotocols) | 同时声明子协议 | 10.1 |
WebSocketFrameAggregator | io.netty.handler.codec.http.websocketx | WebSocketFrameAggregator(int maxContentLength) | 聚合分片帧,超限拒绝 | 10.2 |
TextWebSocketFrame | io.netty.handler.codec.http.websocketx | TextWebSocketFrame(String text) / text() | 文本帧构造与读取 | 10.2 |
BinaryWebSocketFrame | io.netty.handler.codec.http.websocketx | BinaryWebSocketFrame(ByteBuf data) / content() | 二进制帧构造与读取 | 10.2 |
PingWebSocketFrame | io.netty.handler.codec.http.websocketx | PingWebSocketFrame() | 构造 ping 帧,对端应回 pong | 10.3 |
PongWebSocketFrame | io.netty.handler.codec.http.websocketx | PongWebSocketFrame() | 回应 ping | 10.3 |
CloseWebSocketFrame | io.netty.handler.codec.http.websocketx | (int statusCode, String reasonText) | 带状态码与原因的关闭帧 | 10.3 |
ServerHandshakeStateEvent | io.netty.handler.codec.http.websocketx | HANDSHAKE_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。
| 类 | 包 | 成员 | 用途 | 章节 |
|---|---|---|---|---|
Http2FrameCodecBuilder | io.netty.handler.codec.http2 | forServer() / build() | 构造服务端连接级帧编解码器 | 11.1 |
Http2MultiplexHandler | io.netty.handler.codec.http2 | Http2MultiplexHandler(ChannelHandler inboundStreamHandler) | 为每个 stream 创建子 Channel | 11.1 |
Http2StreamFrameToHttpObjectCodec | io.netty.handler.codec.http2 | Http2StreamFrameToHttpObjectCodec(boolean isServer) | 帧与 HTTP 对象互转 | 11.2 |
Http2HeadersFrame | io.netty.handler.codec.http2 | headers() / method() / path() | 读取 HEADERS 帧的伪头 | 11.1 |
Http2HeadersFrame | io.netty.handler.codec.http2 | stream() | 取所属 Http2FrameStream | 11.1 |
Http2DataFrame | io.netty.handler.codec.http2 | content() / endStream() | 读数据帧与流结束标志 | 11.2 |
Http2SettingsFrame | io.netty.handler.codec.http2 | Http2SettingsFrame 类型判断 | 处理连接级 SETTINGS | 11.2 |
Http2GoAwayFrame | io.netty.handler.codec.http2 | errorCode() | 读 GOAWAY 错误码,判断对端为何关闭 | 11.2 |
Http2FrameStream | io.netty.handler.codec.http2 | id() | stream 标识,日志关联请求 | 11.1 |
Http2StreamChannelBootstrap | io.netty.handler.codec.http2 | (Channel ch) / handler(ChannelHandler h) / open() | 在连接上主动开子 Channel | 11.2 |
不要直接把 Http2HeadersFrame 当 FullHttpRequest 用。前者只有头,body 在后续 Http2DataFrame 里;需要完整请求时要么自己聚合,要么挂 Http2StreamFrameToHttpObjectCodec 再配 HttpObjectAggregator。
连接池
连接池的语义是「借出、归还」,不是「缓存 Channel 对象」。FixedChannelPool 限制最大连接数,SimpleChannelPool 不限制;ChannelPoolHandler 在创建、借出、归还三个时点回调,健康检查决定归还的连接还能不能用。
| 类 | 包 | 成员 | 用途 | 章节 |
|---|---|---|---|---|
FixedChannelPool | io.netty.channel.pool | (Bootstrap b, ChannelPoolHandler h, int maxConnections) | 有上限的连接池 | 8.2 |
SimpleChannelPool | io.netty.channel.pool | (Bootstrap b, ChannelPoolHandler h) | 无上限的简单池 | 8.2 |
ChannelPool | io.netty.channel.pool | acquire() | 借出一个 Channel,返回 Future<Channel> | 8.2 |
ChannelPool | io.netty.channel.pool | release(Channel ch) | 归还连接 | 8.2 |
ChannelPool | io.netty.channel.pool | close() | 关闭池并释放所有空闲连接 | 8.2 |
ChannelPoolHandler | io.netty.channel.pool | channelCreated(Channel ch) | 新连接创建时装配 pipeline | 8.2 |
ChannelPoolHandler | io.netty.channel.pool | channelAcquired(Channel ch) / channelReleased(Channel ch) | 借出、归还时重置状态 | 8.2 |
ChannelPoolMap | io.netty.channel.pool | get(SocketAddress key) | 按远端地址取池,多上游场景 | 8.2 |
AbstractChannelPoolMap | io.netty.channel.pool | newPool(SocketAddress key) | 实现按 key 建池的模板方法 | 8.2 |
ChannelHealthChecker | io.netty.channel.pool | ACTIVE | 归还时检查 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) 当字节数 |
| 自动释放的 handler | SimpleChannelInboundHandler<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 再谈请求对象。
- 连接池的核心是借还配对,异常路径不归还,池子会静默枯竭。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。