第3章 Netty的组件和设计
第3章 Netty的组件和设计
Netty源码设计非常优秀。主要体现在技术方面和体系结构方面。
-
- Netty基于Java NIO的异步和事件驱动的实现,保证了高负载下应用程序性能的最大化和可伸缩性。
-
- Netty使用众多设计模式,将应用程序从网络层解耦。
3.1 Channel、EventLoop和ChannelFuture
Channel、EventLoop和ChannelFuture是Netty用于对网络进行的抽象:
-
- Channel ------ Socket
-
- EventLoop ------ 控制流、多线程和并发
-
- ChannelFuture ------ 异步通知
3.1.1 Channel 接口
-
- EmbeddedChannel ----- Embedded传输
-
- LocalServerChannel ----- Local传输
-
- NioDatagramChannel ----- UDP协议NIO传输
-
- NioSctpChannel ----- SCTP协议NIO传输(基于Session) (注:SCTP (Stream Control Transmission Protocol)是一种传输协议,在TCP/IP协议栈中所处的位置和TCP、UDP类似,兼有TCP/UDP两者特征。)
-
- NioSocketChannel ----- TCP协议NIO传输
3.1.2 EventLoop 接口
-
- EventLoop是Netty中非常重要的组件,EventLoop用于处理生命周期中发生的所有事件。
-
- 与EventLoop绑定的Thread称为I/O线程,用于处理整个Channel生命周期中的I/O事件。
-
- 下图说明Channel、EventLoop、Thread以及EventLoopGroup之间的关系
约定俗成的关系(非常重要):
-
- 一个EventLoopGroup包含一个或多个EventLoop
-
- 一个EventLoop在其生命周期内只能和一个Thread绑定
-
- 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理
-
- 一个Channel在其生命周期内,只能注册于一个EventLoop
-
- 一个EventLoop可能被分配处理多个Channel。也就是EventLoop与Channel是1:n的关系
-
- 一个Channel上的所有ChannelHandler的事件由绑定的EventLoop中的I/O线程处理
-
- 不要阻塞Channel的I/O线程,可能会影响该EventLoop中其他Channel事件处理
3.1.3 ChannelFuture 接口
Netty中所有的I/O操作都是异步的,该异步操作可能无法立即得到返回。Netty提供addListener()方法注册回调函数。
-
- 可以将ChannelFuture看作是将来要执行的操作的结果占位符,什么时候被执行,不知道。但肯定会被执行
-
- 属于同一个Channel的操作(回调函数)都被保证将按照注册的顺序执行。
3.2 ChannelHandler 和 ChannelPipeline
3.2.1 ChannelHandler 接口(Netty 的主要组件)
-
- Netty提供了很多扩展的ChannelHandler。如ChannelInboundHandler处理入站事件。
-
- ChannelHandler的方法,就是常说的事件。如:channelActive(链路激活事件)等。所以,ChannelHandler可以说是处理事件的具体业务代码逻辑。
3.2.2 ChannelPipeline 接口
-
- ChannelPipeline本质上是ChannelHandler链的容器
-
- ChannelHandler是处理Channel上的入站和出站事件的代码。
-
- ChannelHandler对象接收事件触发并执行实现的业务逻辑,接着传递给链中的下一个ChannelHandler处理
-
- 请注意下图中头部-尾端,Netty的头部-尾端是规定的,需要记住。
ChannelHandler 安装到 ChannelPipeline 中的过程如下所示:
- 一个ChannelInitializer的实现被注册到了ServerBootstrap中 ;
- 当 ChannelInitializer.initChannel()方法被调用时,ChannelInitializer将在 ChannelPipeline 中安装一组自定义的 ChannelHandler;
- ChannelInitializer 将它自己从 ChannelPipeline 中移除。
上图解释:
-
- 一个入站事件被读取,从ChannelPipeline头部开始流动,传递给第一个ChannelInBoundHandler,这个 ChannelHandler 不一定会实际地修改数据,具体取决于它的具体功能。在这之后,数据将会被传递给链中的下一个ChannelInboundHandler。最终,数据将会到达 ChannelPipeline 的尾端
-
- 一个出站事件触发,从链路尾端的ChannelOutboundHandler开始流动,直到它到达链的头部为止。
注:一个 Netty 应用程序中入站和出站数据流之间的区别。从一个客户端应用程序
的角度来看,如果事件的运动方向是从客户端到服务器端,那么我们称这些事件为出站的,反之
则称为入站的。
3.2.3 channel.write(...)和channelHandlerContext.write(...)区别
-
- channel.write(...) ----- 将会导致消息从ChannelPipeline 的尾端开始流动
-
- channelHandlerContext.write(...) ----- 将导致消息从 ChannelPipeline 中的下一个 ChannelHandler 开始流动
-
- ctx.write(...)的性能优于channel.write(...)
-
- channelHandlerContext.write(...)是将消息从当前的出站队列Hanlder往头部传。 channel.write(...)是从ChannelPipeline的尾部开始往头部传。如果没有理解这点,请看下文。
3.2.4 编码器和解码器
-
- Netty提供多种编码器和解码器,比如:ProtobufDecoder或ProtobufEncoder。所有由 Netty 提供的编码器/解码器适配器类都实现
了 ChannelOutboundHandler 或者 ChannelInboundHandler 接口
- Netty提供多种编码器和解码器,比如:ProtobufDecoder或ProtobufEncoder。所有由 Netty 提供的编码器/解码器适配器类都实现
-
- 编码器/解码器中覆写了channelRead()方法,在方法里调用encode()/decode()方法。再传递给下一个ChannelHandler处理.
-
- 解码器添加在入站事件的头部,编码器添加在出站事件的头部。天然的解决了网络数据的编解码,非常优秀的设计。
3.2.5 抽象类 SimpleChannelInboundHandler
最常见的情况是,你的应用程序会利用一个 ChannelHandler 来接收解码消息,并对该数据应用业务逻辑。
要创建一个这样的 ChannelHandler,你只需要扩展基类 SimpleChannelInboundHandler
在这个 ChannelHandler 中, 你将需要重写基类的一个或者多个方法,并且获取一个到 ChannelHandlerContext 的引用,这个引用将作为输入参数传递给 ChannelHandler 的所有方法。
在这种类型的 ChannelHandler 中,最重要的方法是 channelRead0(ChannelHandlerContext,T),只要不要阻塞当前的 I/O 线程即可。
3.3 引导
Netty有两种类型的引导: 客户端(Bootstrap)和服务端(ServerBootstrap)
-
- Bootstrap(客户端) - 连接远程的主机和端口
-
- ServerBootstrap(服务端) - 两个端口。第一个是本地监听端口,第二个是与tcp连接端口。
-
- 客户端需要一个EventLoopGroup;服务端需要两个EventLoopGroup
3.3.1 服务端需要两个EventLoopGroup
Netty的服务端负责两项任务:
-
- 监听本地端口,等待客户端连接。
-
- 建立客户端通信的临时分配的端口。所以服务端有两个EventLoopGroup,通常称为: bossEventLoopGroup + workerEventLoopGroup.
上图解释:
因为服务器需要两组不同的 Channel。第一组将只包含一个 ServerChannel,代表服务
器自身的已绑定到某个本地端口的正在监听的套接字。而第二组将包含所有已创建的用来处理传
入客户端连接(对于每个服务器已经接受的连接都有一个)的 Channel。
与 ServerChannel 相关联的 EventLoopGroup 将分配一个负责为传入连接请求创建
Channel 的 EventLoop。一旦连接被接受,第二个 EventLoopGroup 就会给它的 Channel
分配一个 EventLoop。
-
- 上图左边的是ServerChannel,用于监听本地端口的通道。对应于bossEventLoopGroup
-
- 右边的是与具体客户端连接的channel,用于数据通信。对应于workerEventLoopGroup
代码:
private EventLoopGroup bossGroup = new NioEventLoopGroup();
private EventLoopGroup workerGroup = new NioEventLoopGroup(2,...);
public void bind(int port) throws InterruptedException {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
ChannelFuture cf = bootstrap.bind(port);
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
LOGGER.info("netty server bind success.");
} else {
LOGGER.error("netty server bind fail.", future.cause());
}
}
});
}
其实传一个EventLoopGroup也是可行的,源码如下
共用了一个EventLoopGroup
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix