「Netty实战 03」大白话 Netty 核心组件分析
大家好,我是 「后端技术进阶」 作者,一个热爱技术的少年。
本文目录:
Bytebuf(字节容器)
Bootstrap 和 ServerBootstrap(启动引导类)
Channel(网络操作抽象类)
EventLoop(事件循环)
EventLoop 介绍
Channel 和 EventLoop 的关系
EventloopGroup 和 EventLoop 的关系
ChannelHandler(消息处理器) 和 ChannelPipeline(ChannelHandler 对象链表)
ChannelFuture(操作执行结果)
觉得不错的话,欢迎 star!ღ( ´・ᴗ・` )比心
Netty 从入门到实战系列文章地址:https://github.com/Snailclimb/netty-practical-tutorial 。 RPC 框架源码地址:https://github.com/Snailclimb/guide-rpc-framework
简单介绍 Netty 最核心的一些组件(对于每一个组件这里不详细介绍)。
通过下面这张图你可以将我提到的这些 Netty 核心组件串联起来。
Bytebuf(字节容器)
网络通信最终都是通过字节流进行传输的。ByteBuf
就是 Netty 提供的一个字节容器,其内部是一个字节数组。 当我们通过 Netty 传输数据的时候,就是通过 ByteBuf
进行的。
我们可以将 ByteBuf
看作是 Netty 对 Java NIO 提供了 ByteBuffer
字节容器的封装和抽象。
有很多小伙伴可能就要问了 :为什么不直接使用 Java NIO 提供的 ByteBuffer
呢?
因为 ByteBuffer
这个类使用起来过于复杂和繁琐。
Bootstrap 和 ServerBootstrap(启动引导类)
Bootstrap
是客户端的启动引导类/辅助类,具体使用方法如下:
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建客户端启动引导/辅助类:Bootstrap
Bootstrap b = new Bootstrap();
//指定线程模型
b.group(group).
......
// 尝试建立连接
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
// 优雅关闭相关线程组资源
group.shutdownGracefully();
}
ServerBootstrap
客户端的启动引导类/辅助类,具体使用方法如下:
// 1.bossGroup 用于接收连接,workerGroup 用于具体的处理
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//2.创建服务端启动引导/辅助类:ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
//3.给引导类配置两大线程组,确定了线程模型
b.group(bossGroup, workerGroup).
......
// 6.绑定端口
ChannelFuture f = b.bind(port).sync();
// 等待连接关闭
f.channel().closeFuture().sync();
} finally {
//7.优雅关闭相关线程组资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
从上面的示例中,我们可以看出:
-
Bootstrap
通常使用connet()
方法连接到远程的主机和端口,作为一个 Netty TCP 协议通信中的客户端。另外,Bootstrap
也可以通过bind()
方法绑定本地的一个端口,作为 UDP 协议通信中的一端。 -
ServerBootstrap
通常使用bind()
方法绑定本地的端口上,然后等待客户端的连接。 -
Bootstrap
只需要配置一个线程组—EventLoopGroup
,而ServerBootstrap
需要配置两个线程组—EventLoopGroup
,一个用于接收连接,一个用于具体的 IO 处理。
Channel(网络操作抽象类)
Channel
接口是 Netty 对网络操作抽象类。通过 Channel
我们可以进行 I/O 操作。
一旦客户端成功连接服务端,就会新建一个 Channel
同该用户端进行绑定,示例代码如下:
// 通过 Bootstrap 的 connect 方法连接到服务端
public Channel doConnect(InetSocketAddress inetSocketAddress) {
CompletableFuture<Channel> completableFuture = new CompletableFuture<>();
bootstrap.connect(inetSocketAddress).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
completableFuture.complete(future.channel());
} else {
throw new IllegalStateException();
}
});
return completableFuture.get();
}
比较常用的Channel
接口实现类是 :
-
NioServerSocketChannel
(服务端) -
NioSocketChannel
(客户端)
这两个 Channel
可以和 BIO 编程模型中的ServerSocket
以及Socket
两个概念对应上。
EventLoop(事件循环)
EventLoop 介绍
这么说吧!EventLoop
(事件循环)接口可以说是 Netty 中最核心的概念了!
《Netty 实战》这本书是这样介绍它的:
EventLoop
定义了 Netty 的核心抽象,用于处理连接的生命周期中所发生的事件。
是不是很难理解?说实话,我学习 Netty 的时候看到这句话是没太能理解的。
说白了,EventLoop
的主要作用实际就是责监听网络事件并调用事件处理器进行相关 I/O 操作(读写)的处理。
Channel 和 EventLoop 的关系
那 Channel
和 EventLoop
直接有啥联系呢?
Channel
为 Netty 网络操作(读写等操作)抽象类,EventLoop
负责处理注册到其上的Channel
的 I/O 操作,两者配合进行 I/O 操作。
EventloopGroup 和 EventLoop 的关系
EventLoopGroup
包含多个 EventLoop
(每一个 EventLoop
通常内部包含一个线程),它管理着所有的 EventLoop
的生命周期。
并且,EventLoop
处理的 I/O 事件都将在它专有的 Thread
上被处理,即 Thread
和 EventLoop
属于 1 : 1 的关系,从而保证线程安全。
下图是 Netty NIO 模型对应的 EventLoop
模型。通过这个图应该可以将EventloopGroup
、EventLoop
、 Channel
三者联系起来。
https://www.jianshu.com/p/128ddc36e713
ChannelHandler(消息处理器) 和 ChannelPipeline(ChannelHandler 对象链表)
下面这段代码使用过 Netty 的小伙伴应该不会陌生,我们指定了序列化编解码器以及自定义的 ChannelHandler
处理消息。
b.group(eventLoopGroup)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcResponse.class));
ch.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcRequest.class));
ch.pipeline().addLast(new KryoClientHandler());
}
});
ChannelHandler
是消息的具体处理器,主要负责处理客户端/服务端接收和发送的数据。
当 Channel
被创建时,它会被自动地分配到它专属的 ChannelPipeline
。一个Channel
包含一个 ChannelPipeline
。ChannelPipeline
为 ChannelHandler
的链,一个 pipeline 上可以有多个 ChannelHandler
。
我们可以在 ChannelPipeline
上通过 addLast()
方法添加一个或者多个ChannelHandler
(一个数据或者事件可能会被多个 Handler 处理) 。当一个 ChannelHandler
处理完之后就将数据交给下一个 ChannelHandler
。
当 ChannelHandler
被添加到的 ChannelPipeline
它得到一个 ChannelHandlerContext
,它代表一个 ChannelHandler
和 ChannelPipeline
之间的“绑定”。ChannelPipeline
通过 ChannelHandlerContext
来间接管理 ChannelHandler
。
https://www.javadoop.com/post/netty-part-4
ChannelFuture(操作执行结果)
public interface ChannelFuture extends Future<Void> {
Channel channel();
ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> var1);
......
ChannelFuture sync() throws InterruptedException;
}
Netty 是异步非阻塞的,所有的 I/O 操作都为异步的。
因此,我们不能立刻得到操作是否执行成功,但是,你可以通过 ChannelFuture
接口的 addListener()
方法注册一个 ChannelFutureListener
,当操作执行成功或者失败时,监听就会自动触发返回结果。
ChannelFuture f = b.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
System.out.println("连接成功!");
} else {
System.err.println("连接失败!");
}
}).sync();
并且,你还可以通过ChannelFuture
的 channel()
方法获取连接相关联的Channel
。
Channel channel = f.channel();
另外,我们还可以通过 ChannelFuture
接口的 sync()
方法让异步的操作编程同步的。
//bind()是异步的,但是,你可以通过 `sync()`方法将其变为同步。
ChannelFuture f = b.bind(port).sync();
后记
往期推荐