本文共 13302 字,大约阅读时间需要 44 分钟。
引导类的层次结构包括一个抽象的父类和两个具体的引导子类:
为什么引导类是 Cloneable 的?
注意,这种方式只会创建引导类实例的EventLoopGroup的一个浅拷贝,所以,后者将在所有克隆的Channel实例之间共享。这是可以接受的,因为通常这些克隆的Channel的生命周期都很短暂,一 个典型的场景是——创建一个Channel以进行一次HTTP请求。
AbstractBootstrap 类的完整声明是: public abstract class AbstractBootstrap <B extends AbstractBootstrap<B,C>,C extends Channel>
其子类的声明如下:
public class Bootstrap extends AbstracBootstrappublic class ServerBootstrap extends AbstractBootstrap
Bootstrap 类被用于客户端或者使用了无连接协议的应用程序中。
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() { //设置用于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(); } } }); }
对于 NIO 以及 OIO 传输两者来说,都有相关的 EventLoopGroup 和 Channel 实现:
注意:
在引导的过程中,在调用 bind()或者 connect()方法之前,必须调用以下方法来设置所需的组件:
如果不这样做,则将会导致 IllegalStateException。对 handler()方法的调用尤其重要,因为它需要配置好 ChannelPipeline。
ServerBootstrap类的方法:
下图展示了 ServerBootstrap 在 bind()方法被调用时创建了一个 ServerChannel, 并且该 ServerChannel 管理了多个子 Channel:
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() { @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(); } } }); }
假设你的服务器正在处理一个客户端的请求,这个请求需要它充当第三方系统的客户端。当 一个应用程序(如一个代理服务器)必须要和组织现有的系统(如 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() { 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 () { @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(); } } }); }
在引导的过程中调用了 handler()或者 child-
Handler()方法来添加单个的 ChannelHandler。这对于简单的应用程序来说可能已经足够了,但是它不能满足更加复杂的需求。例如,一个必须要支持多种协议的应用程序将会有很多的 ChannelHandler,而不会是一个庞大而又笨重的类。正如你经常所看到的一样,你可以根据需要,通过在 ChannelPipeline 中将它们链接在一起来 部署尽可能多的 ChannelHandler。但是,如果在引导的过程中你只能设置一个 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{ @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 中。
在每个 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 AttributeKeyid = new AttributeKey ("ID"); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(new NioEventLoopGroup()) .channel(NioSocketChannel.class) .handler(new SimpleChannelInboundHandler () { @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(); }
前面的引导代码示例使用的都是基于 TCP 协议的 SocketChannel,但是 Bootstrap 类 也可以被用于无连接的协议。为此,Netty 提供了各种 DatagramChannel 的实现。唯一区别就 是,不再调用 connect()方法,而是只调用 bind()方法:
public static void main(String[] args) { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(new OioEventLoopGroup()) .channel(OioDatagramChannel.class) .handler( new SimpleChannelInboundHandler() { @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { //Do somethind with the packet } }); //调用bind方法,因为该协议是无连接的 ChannelFuture future = bootstrap.bind(new InetSocketAddress(0)); future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) System.out.println("Channel bound"); else { System.out.println("Bind attempt failed"); future.cause().printStackTrace(); } } }); }
引导使你的应用程序启动并且运行起来,但是迟早你都需要优雅地将它关闭。当然,你也可 以让 JVM 在退出时处理好一切,但是这不符合优雅的定义,优雅是指干净地释放资源。关闭 Netty 应用程序并没有太多的魔法,但是还是有些事情需要记在心上。
最重要的是,你需要关闭 EventLoopGroup,它将处理任何挂起的事件和任务,并且随后 释放所有活动的线程。这就是调用EventLoopGroup.shutdownGracefully()
方法的作用。 这个方法调用将会返回一个 Future,这个 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 本身。
转载地址:http://mlpqb.baihongyu.com/