Bootstrap类
引导类的层次结构包括一个抽象的父类和两个具体的引导子类:
相对于将具体的引导类分别看作用于服务器和客户端的引导来说,记住它们的本意是用来支撑不同的应用程序的功能的将有所裨益。也就是说,服务器致力于使用一个父 Channel 来接受来自客户端的连接,并创建子 Channel 以用于它们之间的通信;而客户端将最可能只需要一个 单独的、没有父 Channel 的 Channel 来用于所有的网络交互。(正如同我们将要看到的,这也适用于无连接的传输协议,如 UDP,因为它们并不是每个连接都需要一个单独的 Channel。)
为什么引导类是 Cloneable 的?
你有时可能会需要创建多个具有类似配置或者完全相同配置的Channel。为了支持这种模式而又不需要为每个Channel都创建并配置一个新的引导类实例,AbstractBootstrap被标记为了 Cloneable。在一个已经配置完成的引导类实例上调用clone()方法将返回另一个可以立即使用的引 导类实例。
注意,这种方式只会创建引导类实例的EventLoopGroup的一个浅拷贝,所以,后者将在所有克隆的Channel实例之间共享。这是可以接受的,因为通常这些克隆的Channel的生命周期都很短暂,一 个典型的场景是——创建一个Channel以进行一次HTTP请求。
AbstractBootstrap 类的完整声明是:
public abstract class AbstractBootstrap <B extends AbstractBootstrap<B,C>,C extends Channel>
在这个签名中,子类型 B 是其父类型的一个类型参数,因此可以返回到运行时实例的引用以支持方法的链式调用(也就是所谓的流式语法)。
其子类的声明如下:
public class Bootstrap extends AbstracBootstrap<Bootstrap,Channel> public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap,ServerChannel>
启动一个Bootstrap,大致有8步,如下图:
引导客户端和无连接协议
Bootstrap 类被用于客户端或者使用了无连接协议的应用程序中。
下表是 Bootstrap 的常用方法,其中很多是继承自 AbstractBootstrap。
Table 1 Bootstrap methods
名称 | 描述 |
---|---|
group | 设置 EventLoopGroup 用于处理所有的 Channel 的事件 |
channel channelFactory | channel() 指定 Channel 的实现类。如果类没有提供一个默认的构造函数,你可以调用 channelFactory() 来指定一个工厂类被 bind() 调用。 |
localAddress | 指定应该绑定到本地地址 Channel。如果不提供,将由操作系统创建一个随机的。或者,您可以使用 bind() 或 connect()指定localAddress |
option | 设置 ChannelOption 应用于 新创建 Channel 的 ChannelConfig。这些选项将被 bind 或 connect 设置在通道,这取决于哪个被首先调用。这个方法在创建管道后没有影响。所支持 ChannelOption 取决于使用的管道类型。请参考9.6节和 ChannelConfig 的 API 文档 的 Channel 类型使用。 |
attr | 这些选项将被 bind 或 connect 设置在通道,这取决于哪个被首先调用。这个方法在创建管道后没有影响。请参考9.6节。 |
handler | 设置添加到 ChannelPipeline 中的 ChannelHandler 接收事件通知。 |
clone | 创建一个当前 Bootstrap的克隆拥有原来相同的设置。 |
remoteAddress | 设置远程地址。此外,您可以通过 connect() 指定 |
connect | 连接到远端,返回一个 ChannelFuture, 用于通知连接操作完成 |
bind | 将通道绑定并返回一个 ChannelFuture,用于通知绑定操作完成后,必须调用 Channel.connect() 来建立连接。 |
引导客户端
Bootstrap 类负责为客户端和使用无连接协议的应用程序创建 Channel:
public static void main(String[] args) { EventLoopGroup group = new NioEventLoopGroup(); //创建一个Bootstrap类的实例以创建和连接新的客户端Channel Bootstrap bootstrap = new Bootstrap(); //设置EventLoopGroup提供用于处理Channel事件的EventLoop bootstrap.group(group) .channel(NioSocketChannel.class)//指定要使用的Channel实现 .handler(new SimpleChannelInboundHandler<ByteBuf>() {//设置用于Channel事件和数据的ChannelInboundHandler @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("Received data"); } }); //连接到远程主机 ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost", 8080)); future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { System.out.println("Connection established"); } else { System.out.println("Connection attempt failed"); future.cause().printStackTrace(); } } }); }
Channel和EventLoopGroup的兼容性
对于 NIO 以及 OIO 传输两者来说,都有相关的 EventLoopGroup 和 Channel 实现:
hannel 的实现和 EventLoop 的处理过程在 EventLoopGroup 中必须兼容。经验显示,相兼容的实现一般在同一个包下面,例如使用NioEventLoop,NioEventLoopGroup 和 NioServerSocketChannel 在一起。请注意,这些都是前缀“Nio”,然后不会用这些代替另一个实现和另一个前缀,如“Oio”。
记住,EventLoop 分配给该 Channel 负责处理 Channel 的所有操作。当你执行一个方法,该方法返回一个 ChannelFuture ,它将在 分配给 Channel 的 EventLoop 执行。
EventLoopGroup 包含许多 EventLoops 和分配一个 EventLoop 通道时注册。
注意:在引导的过程中,在调用 bind()或者 connect()方法之前,必须调用以下方法来设置所需的组件:
- group();
- channel()或者 channelFactory();
- handler()。
如果不这样做,则将会导致 IllegalStateException。对 handler()方法的调用尤其重要,因为它需要配置好 ChannelPipeline。
引导服务器
ServerBootstrap类的方法:
Table 2 Methods of ServerBootstrap
名称 | 描述 |
---|---|
group | 设置 EventLoopGroup 用于 ServerBootstrap。这个 EventLoopGroup 提供 ServerChannel 的 I/O 并且接收 Channel |
channel channelFactory | channel() 指定 Channel 的实现类。如果管道没有提供一个默认的构造函数,你可以提供一个 ChannelFactory。 |
localAddress | 指定 ServerChannel 实例化的类。如果不提供,将由操作系统创建一个随机的。或者,您可以使用 bind() 或 connect()指定localAddress |
option | 指定一个 ChannelOption 来用于新创建的 ServerChannel 的 ChannelConfig 。这些选项将被设置在管道的 bind() 或 connect(),这取决于谁首先被调用。在此调用这些方法之后设置或更改 ChannelOption 是无效的。所支持 ChannelOption 取决于使用的管道类型。请参考9.6节和 ChannelConfig 的 API 文档 的 Channel 类型使用。 |
childOption | 当管道已被接受,指定一个 ChannelOption 应用于 Channel 的 ChannelConfig。 |
attr | 指定 ServerChannel 的属性。这些属性可以被 管道的 bind() 设置。当调用 bind() 之后,修改它们不会生效。 |
childAttr | 应用属性到接收到的管道上。后续调用没有效果。 |
handler | 设置添加到 ServerChannel 的 ChannelPipeline 中的 ChannelHandler。 具体详见 childHandler() 描述 |
childHandler | 设置添加到接收到的 Channel 的 ChannelPipeline 中的 ChannelHandler。handler() 和 childHandler()之间的区别是前者是接收和处理ServerChannel,同时 childHandler() 添加处理器用于处理和接收 Channel。后者代表一个套接字绑定到一个远端。 |
clone | 克隆 ServerBootstrap 用于连接到不同的远端,通过设置相同的原始 ServerBoostrap。 |
bind | 绑定 ServerChannel 并且返回一个 ChannelFuture,用于 通知连接操作完成了(结果可以是成功或者失败) |
引导一个服务器
ServerBootstrap 中的 childHandler(), childAttr() 和 childOption() 是常用的服务器应用的操作。具体来说,ServerChannel实现负责创建子 Channel,它代表接受连接。因此 引导 ServerChannel 的 ServerBootstrap ,提供这些方法来简化接收的 Channel 对 ChannelConfig 应用设置的任务。
ServerChannel 的实现负责创建子 Channel,这些子 Channel 代表了已被接受的连接。因此,负责引导 ServerChannel 的 ServerBootstrap 提供了这些方法,以简化将设置应用到 已被接受的子 Channel 的 ChannelConfig 的任务。
下图展示了 ServerBootstrap 在 bind()方法被调用时创建了一个 ServerChannel, 并且该 ServerChannel 管理了多个子 Channel:
下面为bind的时序图:
public static void main(String[] args) { NioEventLoopGroup group = new NioEventLoopGroup(); //创建ServerBootstrap ServerBootstrap bootstrap = new ServerBootstrap(); //设置EventLoopGroup,其提供了用于处理Channel事件的EventLoop bootstrap.group(group) .channel(NioServerSocketChannel.class) .childHandler(new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("Reveived data"); } }); //通过配置好的ServerBootstrap的实例绑定该Channel ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { System.out.println("Server bound"); } else { System.out.println("Bound attempt failed"); future.cause().printStackTrace(); } } }); }
从Channel引导客户端
假设你的服务器正在处理一个客户端的请求,这个请求需要它充当第三方系统的客户端。当 一个应用程序(如一个代理服务器)必须要和组织现有的系统(如 Web 服务或者数据库)集成 时,就可能发生这种情况。在这种情况下,将需要从已经被接受的子 Channel 中引导一个客户 端 Channel。
可以按照上面所描述的方式创建新的 Bootstrap 实例,但是这并不是最高效的解决方案,因为它将要求你为每个新创建的客户端 Channel 定义另一个 EventLoop。这会产生 额外的线程,以及在已被接受的子 Channel 和客户端 Channel 之间交换数据时不可避免的上 下文切换。
一个更好的解决方案是:通过将已被接受的子 Channel 的 EventLoop 传递给 Bootstrap 的 group()方法来共享该 EventLoop。因为分配给 EventLoop 的所有 Channel 都使用同一 个线程,所以这避免了额外的线程创建,以及前面所提到的相关的上下文切换,如下图所示:
实现 EventLoop 共享涉及通过调用 group()方法来设置 EventLoop:
public static void main(String[] args) { //创建ServerBootstrap以创建ServerSocketChannel,并绑定它 ServerBootstrap bootstrap = new ServerBootstrap(); //设置EventLoopGroup,其将提供用以处理Channel事件的EventLoop bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .childHandler(//设置用于处理已被接收的子Channel的I/O和数据的ChannelInboundHandler new SimpleChannelInboundHandler<ByteBuf>() { ChannelFuture connectFuture; @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //创建一个Bootstrap类的实例以连接到远程主机 Bootstrap bootstrap = new Bootstrap(); //指定Channel的实现,为入站I/O设置ChannelInboundHandler bootstrap.channel(NioSocketChannel.class).handler( new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("Received data"); } }); //使用与分配给已被接受的子Channel相同的EventLoop bootstrap.group(ctx.channel().eventLoop()); //连接到远程端点 connectFuture = bootstrap.connect( new InetSocketAddress("localhost", 80)); } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { if (connectFuture.isDone()) { //当连接完成时,执行一些数据操作(如代理) } } }); //通过配置好的ServerBootstrap绑定该ServerSocketChannel ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { System.out.println("Server bound"); } else { System.out.println("Bind attempt failed"); future.cause().printStackTrace(); } } }); }
引导过程中添加多个ChannelHandler在引导的过程中调用了 handler()或者 child-Handler()方法来添加单个的 ChannelHandler。这对于简单的应用程序来说可能已经足够了,但是它不能满足更加复杂的需求。例如,一个必须要支持多种协议的应用程序将会有很多的 ChannelHandler,而不会是一个庞大而又笨重的类。你可以根据需要,通过在 ChannelPipeline 中将它们链接在一起来 部署尽可能多的 ChannelHandler。
Netty 提供了一个特殊的 ChannelInboundHandlerAdapter 子类:
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter
它定义了下面的方法:
protected abstract void initChannel(C ch) throws Exception;
这个方法提供了一种将多个 ChannelHandler 添加到一个 ChannelPipeline 中的简便 方法。你只需要简单地向 Bootstrap 或 ServerBootstrap 的实例提供你的 ChannelInitializer 实现即可,并且一旦 Channel 被注册到了它的 EventLoop 之后,就会调用你的 initChannel()版本。在该方法返回之后,ChannelInitializer 的实例将会从 ChannelPipeline 中移除它自己。
下面代码定义了ChannelInitializerImpl 类 , 并 通 过 ServerBootstrap 的 childHandler()方法注册它。你可以看到,这个看似复杂的操作实际上是相当简单直接的:
public class ChannelInitializerDemo { public static void main(String[] args) throws InterruptedException { //创建ServerBootstrap以创建和绑定新的Channel ServerBootstrap bootstrap = new ServerBootstrap(); //设置EventLoopGroup,其将提供用以处理Channel事件的EventLoop bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializerImpl()); ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); future.sync(); } } final class ChannelInitializerImpl extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpClientCodec()); pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE)); } }
如果你的应用程序使用了多个 ChannelHandler,请定义你自己的 ChannelInitializer 实现来将它们安装到 ChannelPipeline 中。
使用Netty的ChannelOption和属性
在每个 Channel 创建时都手动配置它可能会变得相当乏味。幸运的是,你不必这样做。相 反,你可以使用 option()方法来将 ChannelOption 应用到引导。你所提供的值将会被自动 应用到引导所创建的所有 Channel。可用的 ChannelOption 包括了底层连接的详细信息,如 keep-alive 或者超时属性以及缓冲区设置。
Netty 应用程序通常与组织的专有软件集成在一起,而像 Channel 这样的组件可能甚至会在 正常的 Netty 生命周期之外被使用。在某些常用的属性和数据不可用时,Netty 提供了 AttributeMap 抽象(一个由 Channel 和引导类提供的集合)以及 AttributeKey(一 个用于插入和获取属性值的泛型类)。使用这些工具,便可以安全地将任何类型的数据项与客户 端和服务器 Channel(包含 ServerChannel 的子 Channel)相关联了。
例如,考虑一个用于跟踪用户和 Channel 之间的关系的服务器应用程序。这可以通过将用 户的 ID 存储为 Channel 的一个属性来完成。类似的技术可以被用来基于用户的 ID 将消息路由 给用户,或者关闭活动较少的 Channel。
下面的代码展示了可以如何使用 ChannelOption 来配置 Channel,以及如果使用属性 来存储整型值:
public static void main(String[] args) { //创建一个AttributeKey以标识该属性 final AttributeKey<Integer> id = new AttributeKey<Integer>("ID"); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(new NioEventLoopGroup()) .channel(NioSocketChannel.class) .handler(new SimpleChannelInboundHandler<ByteBuf>() { @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { //使用 AttributeKey 检索 属性以及它的值 Integer idValue = ctx.channel().attr(id).get(); //do something with the idValue } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("Received data"); } }); // 设置 ChannelOption, 其将在 connect()或者 bind()方法被调用时 被设置到已经创建的 Channel 上 bootstrap.option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); //存储该id属性 bootstrap.attr(id, 123456); ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost", 80)); future.syncUninterruptibly(); }
关闭Netty
当引导我们的应用程序启动和运行之后,我们也需要懂得如何关闭它。虽然您可以使用JVM来处理所有退出,但是这样做并不能很干净地释放资源。如果要关闭一个Netty的应用程序,主要是记住关闭 EventLoopGroup,将处理任何悬而未决的事件和任务并随后释放所有活动线程。这只是一种叫EventLoopGroup.shutdownGracefully()。这个调用将返回一个 Future 用来通知关闭完成。注意,shutdownGracefully()也是一个异步操作,所以你需要阻塞,直到它完成或注册一个侦听器直到返回的 Future 来通知完成。
需要注意的是, shutdownGracefully()方法也是一个异步的操作,所以你需要阻塞等待直到它完成,或者向 所返回的 Future 注册一个监听器以在关闭完成时获得通知。
//优雅的关闭 EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class); ... // shutdownGracefully()方法将释放 所有的资源,并且关闭所有的当 前正在使用中的 Channel Future<?> future = group.shutdownGracefully(); //block until the group hai shutdown future.syncUninterruptibly();
也可以在调用 EventLoopGroup.shutdownGracefully()方法之前,显式地 在所有活动的 Channel 上调用 Channel.close()方法。但是在任何情况下,都请记得关闭 EventLoopGroup 本身。
参考链接:
https://blog.csdn.net/cold___play/article/details/106771494
https://www.w3cschool.cn/essential_netty_in_action/essential_netty_in_action-r2jf28ca.html