Netty学习系列之三
本文是Netty学习路线的第三篇,重点讲解Netty的核心概念和组件,帮助你理解Netty的设计思想和架构。
在前两篇文章中,我们分别介绍了Java基础与网络编程基础,以及Java NIO的核心概念。这些都为我们学习Netty打下了坚实基础。本篇文章将深入探讨Netty的核心概念,包括Netty的架构设计、启动引导、核心组件等内容,这将帮助我们更好地理解和使用Netty框架。
Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端程序。它极大地简化了TCP、UDP等网络编程的复杂性,同时提供了优异的性能和可扩展性。
┌─────────────────────────────────┐
│ Application │
└─────────────────────────────────┘
▲
│
┌─────────────────────────────────┐
│ Codec & Handlers │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │Encoder│ │Decoder│ │Handler│ │
│ └───────┘ └───────┘ └───────┘ │
└─────────────────────────────────┘
▲
│
┌─────────────────────────────────┐
│ ChannelPipeline (Core) │
└─────────────────────────────────┘
▲
│
┌─────────────────────────────────┐
│ EventLoop & Channel │
└─────────────────────────────────┘
▲
│
┌─────────────────────────────────┐
│ Transport │
│ (TCP/UDP/Local/Embedded etc.) │
└─────────────────────────────────┘
Netty的架构分为几个关键层次:
Bootstrap是Netty应用程序的启动引导类,用于配置和启动Netty应用程序。
特性 | Bootstrap | ServerBootstrap |
---|---|---|
用途 | 客户端应用程序 | 服务器应用程序 |
通道类型 | 单个Channel | 父子Channel模式 |
事件循环组 | 需要一个EventLoopGroup | 需要两个EventLoopGroup |
// 创建两个事件循环组
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 接收连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理IO事件
try {
// 创建服务器启动引导
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup) // 设置事件循环组
.channel(NioServerSocketChannel.class) // 设置通道类型
.option(ChannelOption.SO_BACKLOG, 128) // 设置套接字选项
.childOption(ChannelOption.SO_KEEPALIVE, true) // 子通道选项
.childHandler(new ChannelInitializer<SocketChannel>() { // 子通道处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoServerHandler()); // 添加业务处理器
}
});
// 绑定端口并启动服务器
ChannelFuture future = bootstrap.bind(8080).sync();
System.out.println("Server started on port 8080");
// 等待服务器关闭
future.channel().closeFuture().sync();
} finally {
// 优雅关闭事件循环组
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
// 创建事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建客户端启动引导
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group) // 设置事件循环组
.channel(NioSocketChannel.class) // 设置通道类型
.option(ChannelOption.TCP_NODELAY, true) // 设置套接字选项
.handler(new ChannelInitializer<SocketChannel>() { // 通道处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler()); // 添加业务处理器
}
});
// 连接服务器
ChannelFuture future = bootstrap.connect("localhost", 8080).sync();
System.out.println("Connected to server");
// 等待连接关闭
future.channel().closeFuture().sync();
} finally {
// 优雅关闭事件循环组
group.shutdownGracefully();
}
Bootstrap和ServerBootstrap支持丰富的配置选项:
option(ChannelOption.xxx, value)
childOption(ChannelOption.xxx, value)
SO_BACKLOG
:服务器接受连接的队列长度SO_KEEPALIVE
:保持TCP连接活跃TCP_NODELAY
:禁用Nagle算法,降低延迟SO_RCVBUF
/SO_SNDBUF
:接收/发送缓冲区大小CONNECT_TIMEOUT_MILLIS
:连接超时时间Channel是Netty中的核心抽象,代表与网络套接字或I/O操作组件的连接。Channel的生命周期包含以下状态:
主要Channel实现:
NioSocketChannel
:客户端TCP通道NioServerSocketChannel
:服务器TCP通道NioDatagramChannel
:UDP通道LocalChannel
:JVM内通信通道EmbeddedChannel
:测试用通道ChannelPipeline是ChannelHandler的链式容器,负责处理或拦截Channel的入站和出站事件。
I/O请求
▲
│
┌─────────────────────────────────────────────────────────────────────────────┐
│ ChannelPipeline │
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ │ │ │ │ │ │ │ │
│ │ Handler1 │ │ Handler2 │ │ Handler3 │ │ Handler4 │ │
│ │ │ │ │ │ │ │ │ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │ │ │
│ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ │
│ │ Context1 │ │ Context2 │ │ Context3 │ │ Context4 │ │
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
▲ │
│ ▼
入站事件流 出站事件流
(读操作、状态变化等) (写操作、连接操作等)
ChannelPipeline是一个双向链表结构,有两种事件流向:
// 获取Channel的Pipeline
ChannelPipeline pipeline = channel.pipeline();
// 添加处理器
pipeline.addLast("decoder", new StringDecoder()); // 末尾添加
pipeline.addFirst("encoder", new StringEncoder()); // 头部添加
pipeline.addAfter("decoder", "handler", new MyHandler()); // 在特定处理器后添加
// 移除处理器
pipeline.remove("encoder");
pipeline.remove(stringDecoder);
// 替换处理器
pipeline.replace("decoder", "newDecoder", new MyDecoder());
// 获取处理器
ChannelHandler handler = pipeline.get("handler");
// 触发自定义事件
pipeline.fireChannelRead(Unpooled.copiedBuffer("Hello", CharsetUtil.UTF_8));
ChannelHandlerContext代表ChannelHandler和ChannelPipeline之间的关联,主要用于:
public class MyHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 获取Channel
Channel channel = ctx.channel();
// 获取Pipeline
ChannelPipeline pipeline = ctx.pipeline();
// 仅调用下一个处理器,不传递给整个Pipeline
ctx.fireChannelRead(msg);
// 写数据并刷新
ctx.writeAndFlush(msg);
}
}
ChannelHandler是Netty中最核心的组件之一,用于处理I/O事件或拦截I/O操作,并转发到ChannelPipeline中的下一个处理器。
┌─────────────┐
│ChannelHandler│
└──────┬──────┘
│
┌───────────────┴───────────────┐
│ │
┌───────▼───────┐ ┌────────▼─────────┐
│ChannelInbound │ │ChannelOutbound │
│ Handler │ │ Handler │
└───────┬───────┘ └────────┬─────────┘
│ │
┌───────────▼───────────┐ ┌───────────▼───────────────┐
│ChannelInboundHandler │ │ChannelOutboundHandler │
│ Adapter │ │ Adapter │
└─────────────────────┬─┘ └───────────────────────────┘
│
┌─────────────▼────────────┐
│SimpleChannelInboundHandler│
└──────────────────────────┘
主要Handler类型:
入站处理器的主要方法:
public class CustomInboundHandler extends ChannelInboundHandlerAdapter {
// 当Channel被注册到EventLoop
@Override
public void channelRegistered(ChannelHandlerContext ctx) {}
// 当Channel从EventLoop注销
@Override
public void channelUnregistered(ChannelHandlerContext ctx) {}
// 当Channel变为活动状态(连接建立)
@Override
public void channelActive(ChannelHandlerContext ctx) {}
// 当Channel变为非活动状态(连接关闭)
@Override
public void channelInactive(ChannelHandlerContext ctx) {}
// 当从Channel读取数据
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {}
// 当Channel读取完成
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {}
// 当用户事件触发
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {}
// 当Channel可写状态变化
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) {}
// 当处理过程中发生异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {}
}
出站处理器的主要方法:
public class CustomOutboundHandler extends ChannelOutboundHandlerAdapter {
// 当请求绑定本地地址
@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {}
// 当请求连接远程主机
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {}
// 当请求断开连接
@Override
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) {}
// 当请求关闭Channel
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) {}
// 当请求注销Channel
@Override
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) {}
// 当请求读取数据
@Override
public void read(ChannelHandlerContext ctx) {}
// 当请求写入数据
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {}
// 当请求刷新数据
@Override
public void flush(ChannelHandlerContext ctx) {}
}
编解码器是特殊的ChannelHandler,用于将字节序列转换为Java对象(解码器),或将Java对象转换为字节序列(编码器)。
ByteToMessageDecoder
:将字节转换为消息ReplayingDecoder
:简化的ByteToMessageDecoderMessageToMessageDecoder
:将一种消息类型转换为另一种LineBasedFrameDecoder
:基于行分隔符的解码器DelimiterBasedFrameDecoder
:基于分隔符的解码器LengthFieldBasedFrameDecoder
:基于长度字段的解码器MessageToByteEncoder
:将消息编码为字节MessageToMessageEncoder
:将一种消息类型编码为另一种字符串解码器:
public class StringDecoder extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {
out.add(msg.toString(CharsetUtil.UTF_8));
}
}
字符串编码器:
public class StringEncoder extends MessageToByteEncoder<String> {
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) {
out.writeBytes(msg.getBytes(CharsetUtil.UTF_8));
}
}
自定义消息对象:
public class MyMessage {
private int type;
private String content;
// getter、setter和构造方法省略
}
自定义解码器:
public class MyMessageDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
// 确保有足够的数据可读
if (in.readableBytes() < 8) {
return;
}
// 标记当前读取位置
in.markReaderIndex();
// 读取消息类型和长度
int type = in.readInt();
int length = in.readInt();
// 如果消息体不完整,则重置读取位置,等待更多数据到达
if (in.readableBytes() < length) {
in.resetReaderIndex();
return;
}
// 读取消息内容
byte[] content = new byte[length];
in.readBytes(content);
// 创建消息对象并添加到输出列表
MyMessage message = new MyMessage();
message.setType(type);
message.setContent(new String(content, CharsetUtil.UTF_8));
out.add(message);
}
}
自定义编码器:
public class MyMessageEncoder extends MessageToByteEncoder<MyMessage> {
@Override
protected void encode(ChannelHandlerContext ctx, MyMessage msg, ByteBuf out) {
// 写入消息类型
out.writeInt(msg.getType());
// 获取消息内容的字节数组
byte[] content = msg.getContent().getBytes(CharsetUtil.UTF_8);
// 写入内容长度和内容
out.writeInt(content.length);
out.writeBytes(content);
}
}
ByteBuf是Netty的核心数据容器,对Java NIO的ByteBuffer进行了改进和扩展,提供了更灵活、更高效的API。
ByteBuf的主要特点:
+-------------------------------------------------+
| ByteBuf |
| |
+-------------------+--------------+--------------+
| discardable bytes | readable | writable |
| | bytes | bytes |
+-------------------+--------------+--------------+
| | | |
0 <= readerIndex <= writerIndex <= capacity
ByteBuf的三个重要指标:
按内存分配:
按池化策略:
// 创建ByteBuf
ByteBuf buf = Unpooled.buffer(10); // 堆缓冲区
ByteBuf directBuf = Unpooled.directBuffer(10); // 直接缓冲区
// 写入数据
buf.writeBytes("Hello".getBytes());
buf.writeByte(0);
buf.writeShort(123);
buf.writeInt(456);
// 读取数据
byte[] bytes = new byte[5];
buf.readBytes(bytes); // 读取Hello
byte b = buf.readByte(); // 读取0
short s = buf.readShort(); // 读取123
int i = buf.readInt(); // 读取456
// 随机访问
buf.setByte(0, 'A');
byte value = buf.getByte(0);
// 清空缓冲区
buf.clear(); // 只是重置索引,不清除内容
// 释放缓冲区
buf.release();
零拷贝是一种避免内存复制的技术,在Netty中有多种实现方式:
允许将多个ByteBuf组合成一个逻辑视图,无需物理复制:
CompositeByteBuf composite = Unpooled.compositeBuffer();
ByteBuf header = Unpooled.copiedBuffer("Header", CharsetUtil.UTF_8);
ByteBuf body = Unpooled.copiedBuffer("Body", CharsetUtil.UTF_8);
// 添加缓冲区,不会复制内容
composite.addComponents(header, body);
// 访问组合缓冲区
for (int i = 0; i < composite.numComponents(); i++) {
ByteBuf component = composite.component(i);
System.out.println(component.toString(CharsetUtil.UTF_8));
}
创建ByteBuf的视图,共享底层存储:
ByteBuf buffer = Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8);
// 创建切片
ByteBuf slice1 = buffer.slice(0, 5); // "Netty"
ByteBuf slice2 = buffer.slice(6, 2); // "in"
// 修改切片会影响原缓冲区
slice1.setByte(0, 'J');
assert buffer.getByte(0) == 'J';
高效传输文件内容:
File file = new File("data.txt");
FileChannel fileChannel = new FileInputStream(file).getChannel();
// 创建FileRegion
FileRegion region = new DefaultFileRegion(fileChannel, 0, file.length());
// 写入Channel
channel.writeAndFlush(region).addListener(future -> {
if (future.isSuccess()) {
System.out.println("File transferred successfully");
} else {
System.err.println("File transfer failed");
}
});
EventLoop是Netty的核心抽象,负责处理注册到其中的Channel的所有I/O操作。一个EventLoop在其生命周期内只绑定到一个Thread上。
主要特点:
EventLoopGroup是多个EventLoop的集合,负责创建EventLoop并分配Channel。
常用EventLoopGroup实现:
NioEventLoopGroup
:基于NIO的实现EpollEventLoopGroup
:Linux上基于epoll的实现KQueueEventLoopGroup
:BSD系统上基于kqueue的实现DefaultEventLoopGroup
:通用实现,用于执行非I/O任务Netty服务器通常使用两个EventLoopGroup:
┌─────────────┐
│ │
│ BossGroup │──┐
│ │ │ ┌───────────┐
└─────────────┘ │ │ │
│ │ │ Socket │
▼ │ │ Channel │
┌─────────┐ ┌─────────────┐ │ │ │
│ │ │ │◄──┘ └───────────┘
│ Client │◄────►│ ServerSocket│ ▲
│ │ │ Channel │ │
└─────────┘ └─────────────┘ │
│ │
│ │
▼ │
┌─────────────┐ │
│ │ │
│ WorkerGroup │────────────────┘
│ │
└─────────────┘
EventLoop还支持任务调度:
// 延迟执行
channel.eventLoop().schedule(() -> {
System.out.println("Run after 10 seconds");
}, 10, TimeUnit.SECONDS);
// 定期执行
channel.eventLoop().scheduleAtFixedRate(() -> {
System.out.println("Run every 60 seconds");
}, 0, 60, TimeUnit.SECONDS);
本文介绍了Netty的核心概念,包括Netty的架构设计、Bootstrap启动引导、Channel与ChannelPipeline、ChannelHandler与编解码器、ByteBuf与零拷贝技术以及EventLoop与线程模型。理解这些核心概念是掌握Netty框架的关键。在下一篇文章中,我们将探讨如何使用Netty实现各种基础应用,如HTTP服务器、WebSocket等,敬请期待!
作者:by.G
如需转载,请注明出处