【java入门到放弃】Netty入门
十大组件
Bootstrap / ServerBootstrap 启动器 / 服务端启动器
EventLoop / EventLoopGroup 事件循环/事件循环组
Channel 通道
ChannelPipeline 管道
ChannelHandler 处理器
ChannelHandlerContext 处理器上下文
ChannelFuture 异步结果
ByteBuf 字节缓冲区
概述
Bootstrap / ServerBootstrap 负责将 Channel、EventLoopGroup、ChannelPipeline、ChannelHandler 以及相关配置组合并启动。
EventLoop 是负责 IO 事件和任务执行的单线程执行器,一个 EventLoop 可以管理多个 Channel,而一个 Channel 在生命周期内只绑定一个 EventLoop。EventLoopGroup 是管理多个 EventLoop 的线程池。
每个 Channel 都绑定一个 ChannelPipeline,Pipeline 由多个 ChannelHandlerContext 组成,每个 ChannelHandlerContext 对应一个 ChannelHandler。
当 IO 事件在 Channel 上发生时,事件会沿着 ChannelPipeline 传播,由其中的 ChannelHandler 依次进行处理或拦截。
ChannelHandlerContext 表示当前 Handler 在 Pipeline 中的操作入口和事件传播起点,可用于继续传递事件、写数据以及访问 Channel 和 EventLoop。
一个 Channel 可以产生多个 ChannelFuture,每个 ChannelFuture 只对应一次具体的异步 Channel 操作,用于表示该操作的完成状态(未完成 / 成功 / 失败)。
ByteBuf 是 Netty 中用于网络 IO 的高性能字节容器,负责数据存储、读写指针和内存管理,通常在 Handler 中被读取和处理,用于替代 ByteBuffer。
Bootstrap
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap
//客户端 只需要一个 EventLoopGroup
//负责 connect、read/write、回调handler
.group(group)
//指定用什么Channel 实现
.channel(NioSocketChannel.class)
//客户端只有 handler,没有 childHandler
//为这个Channel初始化并绑定它的ChannelPipeline和其中的 ChannelHandler。
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new ClientHandler());
}
});
//连接服务器
ChannelFuture future = bootstrap.connect("127.0.0.1", 8080).sync();
ServerBootstrap
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 接收连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理 IO
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
// 1. 绑定 boss / worker 线程模型
.group(bossGroup, workerGroup)
// 2. 指定服务端 Channel(监听端口用)
.channel(NioServerSocketChannel.class)
// 3. (可选)ServerChannel 的 handler
.handler(new LoggingHandler(LogLevel.INFO))
// 4. 给“每一个新连接”的 Channel 初始化 Pipeline
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(
new ServerHandler()
);
}
})
// 5. TCP 参数(给子连接用)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 6. 绑定端口(异步)
ChannelFuture future = bootstrap.bind(8080).sync();
// 7. 阻塞等待服务端关闭
future.channel().closeFuture().sync();
} finally {
// 8. 优雅关闭线程池
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
EventLoop/EventLoopGroup
EventLoopGroup:负责创建和管理多个 EventLoop,并在 Channel 注册时为每个 Channel 分配一个 EventLoop
EventLoop:是单线程事件循环,负责监听 IO 就绪并执行对应的 Channel 处理逻辑。Netty把EventLoop藏起来了
// 服务端
// bossGroup:只负责 accept
// 指定分配1个线程
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// workerGroup:负责读写 IO
// 线程数 = CPU 核数 * 2
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
.group(bossGroup, workerGroup) // 服务端专用写法
.channel(NioServerSocketChannel.class);
// 客户端
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(group) // 客户端只有一个 EventLoopGroup
.channel(NioSocketChannel.class);
// 执行普通任务(重点)
group.execute(() -> {
// 这个任务会在某个 EventLoop 线程中执行
// 把任务放进 Netty 的 IO 线程
// 保证和 Channel 在同一线程
});
// 优雅关闭
group.shutdownGracefully();
Channel
通道就是“连接对象”,可以是 TCP 连接、UDP 通道或者 JVM 内部通信管道。
指定Channel
常用通道:
TCP 通道:NioServerSocketChannel、NioSocketChannel
Linux 高性能 TCP:EpollServerSocketChannel / EpollSocketChannel
// 企业实现切换epoll模式
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.Channel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
public class NettyServer {
public static void main(String[] args) {
// 判断当前系统是否支持 epoll
boolean useEpoll = Epoll.isAvailable();
EventLoopGroup bossGroup;
EventLoopGroup workerGroup;
Class<? extends Channel> channelClass;
if (useEpoll) {
// Linux + 支持 epoll
bossGroup = new EpollEventLoopGroup(1);
workerGroup = new EpollEventLoopGroup();
channelClass = EpollServerSocketChannel.class;
} else {
// Windows / macOS / 不支持 epoll
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup();
channelClass = NioServerSocketChannel.class;
}
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
.group(bossGroup, workerGroup)
.channel(channelClass);
}
}
获取Channel
Channel在两个地方使用,在业务 Handler 里,在启动 / 管理代码里。
//在业务 Handler 里获取其Channel
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
Channel channel = ctx.channel();
}
//在启动/管理代码里获取其Channel
//客户段
ChannelFuture future = bootstrap.connect(host, port).sync();
Channel channel = future.channel();
//服务端
ChannelFuture future = serverBootstrap.bind(8080).sync();
Channel serverChannel = future.channel();
//ChannelFuture.channel() 在成功和失败时都存在;
//但只有成功时,这个 Channel 才是“可用的连接”。
使用Channel
//在业务Handler里
//读 / 写业务数据(最核心)
//典型场景:请求 → 响应、推送当前连接的消息、回 ACK / 心跳
ctx.channel().writeAndFlush(response);
//绑定 / 获取业务状态(非常常用)
//用途:登录态、用户 ID、权限信息、协议版本
ctx.channel().attr(USER_ID).set("10086");
//判断连接是否可用
//防止:对已断开的连接写数据、NPE / 无效写
if (ctx.channel().isActive()) {
...
}
//关闭当前连接
//典型原因:协议非法、鉴权失败、超过次数限制
ctx.channel().close();
//拿 EventLoop 做线程安全调度
ctx.channel().eventLoop().execute(() -> {
// 在该 Channel 所属线程执行
});
//在启动 / 管理代码里获取 Channel
//保存 Channel(连接管理)
//用途:多客户端连接、后续主动推送、广播消息
Channel channel = future.channel();
channelGroup.add(channel);
//等待关闭(主线程阻塞)
//用途:服务不退出、Demo / main 方法
//只在 main / 启动代码里用
channel.closeFuture().sync();
//主动关闭连接 / 服务
channel.close();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
ChannelPipeline
ChannelPipeline 是绑定在 Channel 上的一条“责任链”,
用来按顺序处理入站(Inbound)和出站(Outbound)事件。
也是有两个地方使用,Pipeline 初始化阶段:装 Handler;Handler 运行阶段:改 Handler 或触发事件。
//初始化阶段(ChannelInitializer 里)
//初始化 Pipeline(最常见)
ch.pipeline().addLast(new ClientHandler());
//按顺序添加多个 Handler
ChannelPipeline p = ch.pipeline();
p.addLast(new Decoder());
p.addLast(new Encoder());
p.addLast(new BusinessHandler());
//带名字的 Handler
p.addLast("decoder", new Decoder());
p.addLast("encoder", new Encoder());
p.addLast("biz", new BusinessHandler());
//运行阶段(Handler 里,通过 ctx 获取)
//(运行时)插入 Handler
p.addAfter("decoder", "auth", new AuthHandler());
p.addBefore("biz", "log", new LogHandler());
//移除 Handler
p.remove("auth");
p.remove(AuthHandler.class);
//替换 Handler
p.replace("biz", "newBiz", new NewBusinessHandler());
ChannelHander
ChannelHandler = ChannelPipeline 中的数据处理器,它负责“处理和响应事件”,包括入站(读)和出站(写)事件。
Inbound 事件包括: 收到数据(channelRead)、 连接建立(channelActive)、 连接断开(channelInactive)、异常(exceptionCaught) 、心跳 / 空闲(userEventTriggered)
Outbound 事件包括: 写数据(write)、 刷新(flush) 、连接(connect)、 关闭(close)、 绑定端口(bind)
Netty 有 Inbound / Outbound / Duplex 三类 Handler 共用一个 Pipeline;
实际开发中主要自定义 Inbound,Outbound 多由 Encoder 或框架提供,Duplex 只在读写强相关时使用。
直接new的Hander
pipeline.addLast(new IdleStateHandler(60, 0, 0));
//IdleStateHandler(long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit)
//readerIdleTime – 读空闲时间(单位默认秒,如果没指定 TimeUnit)。
//writerIdleTime – 写空闲时间。
//allIdleTime – 读写都空闲时间。
//当超过指定时间没有读/写操作时,会触发 IdleStateEvent。
//你可以在 pipeline 的下一个 handler 中捕获这个事件,做心跳或者断开连接处理。
//不管是读空闲还是写空闲,都是下一个handler捕获
//处理这个事件例子
//重写ChannelInboundHandlerAdapter里的方法
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent e) {
if (e.state() == IdleState.READER_IDLE) {
System.out.println("读空闲超时,可能客户端断开了");
ctx.close(); // 可选择断开连接
}
} else {
super.userEventTriggered(ctx, evt);
}
}
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(65536));
//HttpServerCodec 负责 HTTP 协议解析/编码
//HttpObjectAggregator 负责 把碎片 HTTP 请求拼成一个完整请求
//两个一起用,你的业务 Handler 才能“像用 Servlet 一样”处理 HTTP
pipeline.addLast(new LengthFieldBasedFrameDecoder(...));
//按长度拆包
pipeline.addLast(new DelimiterBasedFrameDecoder(...))
//按分隔符拆包
pipeline.addLast(sslContext.newHandler(ch.alloc()));
//给当前 Channel 加一个 SSL/TLS Handler,让后续所有通信都变成“加密的 HTTPS / TLS 通信”。
//TCP 还是 TCP
//只是多了一层 SSL/TLS 协议
//所有 读到的数据会先被解密
//所有 写出的数据会先被加密
pipeline.addLast(new LoggingHandler(LogLevel.INFO));
//打日志
最常用的类
//ChannelInboundHandlerAdapter(最常用)
//所有方法都有默认实现
//你只 override 你关心的
//业务开发首选
public class MyHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 处理入站数据
ctx.fireChannelRead(msg); // 或者自己消费
}
}
//SimpleChannelInboundHandler<T>(更安全)
//自动释放资源
//类型安全
//非常适合「解码后的业务对象」
//不适合跨 Handler 传 msg 的场景
public class MyHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
// 自动释放 ByteBuf / msg
}
}
//ChannelDuplexHandler(少量场景)
//用于:读写强相关、连接状态统计、统一日志、监控
public class MyDuplexHandler extends ChannelDuplexHandler {
@Override
public void channelRead(...) { }
@Override
public void write(...) { }
}
ChannelHandlerContext
ChannelHandlerContext(简称 ctx)是一个 Handler 在 Pipeline 中的“上下文对象”,它连接着当前 Handler、所属 Channel、Pipeline 以及事件传播机制。
ctx 是每个 Handler 专属的上下文,只能在 Handler 内使用它触发事件或操作 Channel;其他地方只能通过 Channel 或 Pipeline 间接操作。
//使用方法
//Inbound 事件(接收数据/异常)
fireChannelRead(msg) //将消息传给下一个 Inbound Handler
fireChannelReadComplete() //通知下一个 Handler 数据读取完成
fireExceptionCaught(Throwable cause) //将异常沿 Inbound 链传递
fireChannelActive() //触发通道激活事件
fireChannelInactive() //触发通道失效事件
//Outbound 事件(发送数据/控制连接)
write(msg) //将数据沿 Outbound 链传播(不 flush)
flush() //刷新写缓冲区,将数据发送到 Socket
writeAndFlush(msg) //写 + 刷新,常用组合
close() //关闭 Channel
disconnect() //断开连接
ChannelFuture
ChannelFuture 是 Netty 异步 IO 操作的“结果凭证”
三种结果
future.isDone():操作是否完成
future.isSuccess():是否成功
future.cause():如果失败,拿异常
Throwable cause = future.cause();
//返回值类型是Throwable
//如果操作成功 → 返回null
//如果操作失败 → 返回 具体异常对象(IOException、ClosedChannelException 等)
阻塞
Netty 的 IO 是异步的,写、连接、关闭等操作立即返回 ChannelFuture
但有时我们希望阻塞等待操作完成,就需要用 sync() 或 await()。
future.sync(); //被中断会抛出InterruptedException
ChannelFuture future = channel.writeAndFlush(msg).await();
//等待任务结束,如果任务失败,不会抛异常,而是通过 isSuccess 判断
if (future.isSuccess()) {
// 成功
} else {
Throwable cause = future.cause(); // 获取异常
}
与 Channel 的关系
| 特性 | 描述 |
|---|---|
| 一个 Channel 可以有多个 ChannelFuture | 每个异步操作(write、connect、bind、close)都会返回一个 ChannelFuture |
| 一个 ChannelFuture 只对应一次操作 | 比如一次 write、一次 connect |
| 可以通过 ChannelFuture 获取操作对应的 Channel | future.channel() |
使用场景
绑定端口(ServerBootstrap)
ChannelFuture future = serverBootstrap.bind(8080);
future.addListener(f -> {
if(f.isSuccess()) {
System.out.println("端口绑定成功");
} else {
System.err.println("端口绑定失败: " + f.cause());
}
});
发送消息
ChannelFuture writeFuture = ch.writeAndFlush("Hello");
writeFuture.addListener(f -> {
if(f.isSuccess()) {
System.out.println("发送成功");
} else {
System.err.println("发送失败: " + f.cause());
}
});
关闭 Channel
ChannelFuture closeFuture = ch.close();
closeFuture.addListener(f -> System.out.println("Channel 已关闭"));
ByteBuf
ByteBuf 是 Netty 提供的高性能字节容器,用于存储、读取和写入网络数据
ByteBuf 主要出现在 ChannelPipeline 中的 Inbound / Outbound Handler 里,用来承载“正在网络中流动的数据”。
指针和容量
[ 0 … readerIndex … writerIndex … capacity ]
readerIndex:当前可读的位置
writerIndex:当前可写的位置
capacity:总容量
readableBytes() = writerIndex - readerIndex
writableBytes() = capacity - writerIndex
常用操作
//读取数据
ByteBuf buf = ...;
int readable = buf.readableBytes();
// 按顺序读取
byte b = buf.readByte();
int i = buf.readInt();
byte[] bytes = new byte[readable];
buf.readBytes(bytes);
//readXXX 会移动 readerIndex
//写入数据
ByteBuf buf = ...;
buf.writeByte(0x01);
buf.writeInt(100);
buf.writeBytes("Hello".getBytes());
// writeXXX 会移动 writerIndex
//不改变指针的访问(get / set)
byte b = buf.getByte(0); // 不移动 readerIndex
buf.setByte(0, (byte)0xFF); // 不移动 writerIndex
//slice/duplicate(零拷贝)
ByteBuf slice = buf.slice(0, 5); // 引用原始内存,不拷贝;读写指针独立
ByteBuf dup = buf.duplicate(); // 完全复制指针,但共享数据;
更多推荐




所有评论(0)