首页 » Netty实战 » Netty实战全文在线阅读

《Netty实战》第8章 引导

关灯直达底部

本章主要内容

  • 引导客户端和服务器
  • Channel内引导客户端
  • 添加ChannelHandler
  • 使用ChannelOption和属性[1]

在深入地学习了ChannelPipelineChannelHandlerEventLoop之后,你接下来的问题可能是:“如何将这些部分组织起来,成为一个可实际运行的应用程序呢?”

答案是?“引导”(Bootstrapping)。到目前为止,我们对这个术语的使用还比较含糊,现在已经到了精确定义它的时候了。简单来说,引导一个应用程序是指对它进行配置,并使它运行起来的过程——尽管该过程的具体细节可能并不如它的定义那样简单,尤其是对于一个网络应用程序来说。

和它对应用程序体系架构的做法[2]一致,Netty处理引导的方式使你的应用程序[3]和网络层相隔离,无论它是客户端还是服务器。正如同你将要看到的,所有的框架组件都将会在后台结合在一起并且启用。引导是我们一直以来都在组装的完整拼图[4]中缺失的那一块。当你把它放到正确的位置上时,你的Netty应用程序就完整了。

8.1 Bootstrap类

引导类的层次结构包括一个抽象的父类和两个具体的引导子类,如图8-1所示。

图8-1 引导类的层次结构

相对于将具体的引导类分别看作用于服务器和客户端的引导来说,记住它们的本意是用来支撑不同的应用程序的功能的将有所裨益。也就是说,服务器致力于使用一个父Channel来接受来自客户端的连接,并创建子Channel以用于它们之间的通信;而客户端将最可能只需要一个单独的、没有父ChannelChannel来用于所有的网络交互。(正如同我们将要看到的,这也适用于无连接的传输协议,如UDP,因为它们并不是每个连接都需要一个单独的Channel。)

我们在前面的几章中学习的几个Netty组件都参与了引导的过程,而且其中一些在客户端和服务器都有用到。两种应用程序类型之间通用的引导步骤由AbstractBootstrap处理,而特定于客户端或者服务器的引导步骤则分别由BootstrapServerBootstrap处理。

在本章中接下来的部分,我们将详细地探讨这两个类,首先从不那么复杂的Bootstrap类开始。

为什么引导类是Cloneable的

你有时可能会需要创建多个具有类似配置或者完全相同配置的Channel。为了支持这种模式而又不需要为每个Channel都创建并配置一个新的引导类实例,AbstractBootstrap被标记为了Cloneable[5]。在一个已经配置完成的引导类实例上调用clone方法将返回另一个可以立即使用的引导类实例。

注意,这种方式只会创建引导类实例的EventLoopGroup的一个浅拷贝,所以,后者[6]将在所有克隆的Channel实例之间共享。这是可以接受的,因为通常这些克隆的Channel的生命周期都很短暂,一个典型的场景是——创建一个Channel以进行一次HTTP请求。

AbstractBootstrap类的完整声明是:

public abstract class AbstractBootstrap  <B extends AbstractBootstrap<B,C>,C extends Channel>  

在这个签名中,子类型B是其父类型的一个类型参数,因此可以返回到运行时实例的引用以支持方法的链式调用(也就是所谓的流式语法)。

其子类的声明如下:

public class Bootstrap  extends AbstractBootstrap<Bootstrap,Channel>  

public class ServerBootstrap  extends AbstractBootstrap<ServerBootstrap,ServerChannel>  

8.2 引导客户端和无连接协议

Bootstrap类被用于客户端或者使用了无连接协议的应用程序中。表8-1提供了该类的一个概览,其中许多方法都继承自AbstractBootstrap类。

表8-1 Bootstrap类的API

名  称

描  述

Bootstrap group(EventLoopGroup)

设置用于处理Channel所有事件的EventLoopGroup

Bootstrap channel(
  Class<? extends C>)
Bootstrap channelFactory(
  ChannelFactory<? extends C>)

channel方法指定了Channel的实现类。如果该实现类没提供默认的构造函数[7],可以通过调用channel- Factory方法来指定一个工厂类,它将会被bind方法调用

Bootstrap localAddress(
  SocketAddress)

指定Channel应该绑定到的本地地址。如果没有指定,则将由操作系统创建一个随机的地址。或者,也可以通过bind或者connect方法指定localAddress

<T> Bootstrap option(
  ChannelOption<T> option,
  T value)

设置ChannelOption,其将被应用到每个新创建的ChannelChannelConfig。这些选项将会通过bind或者connect方法设置到Channel,不管哪个先被调用。这个方法在Channel已经被创建后再调用将不会有任何的效果。支持的ChannelOption取决于使用的Channel类型。参见8.6节以及ChannelConfig的API文档,了解所使用的Channel类型

<T> Bootstrap attr(
  Attribute<T> key, T value)

指定新创建的Channel的属性值。这些属性值是通过bind或者connect方法设置到Channel的,具体取决于谁最先被调用。这个方法在Channel被创建后将不会有任何的效果。参见8.6节

Bootstrap
handler(ChannelHandler)

设置将被添加到ChannelPipeline以接收事件通知的ChannelHandler

Bootstrap clone

创建一个当前Bootstrap的克隆,其具有和原始的Bootstrap相同的设置信息

Bootstrap remoteAddress(
  SocketAddress)

设置远程地址。或者,也可以通过connect方法来指定它

ChannelFuture connect

连接到远程节点并返回一个ChannelFuture,其将会在连接操作完成后接收到通知

ChannelFuture bind

绑定Channel并返回一个ChannelFuture,其将会在绑定操作完成后接收到通知,在那之后必须调用Channel. connect方法来建立连接

下一节将一步一步地讲解客户端的引导过程。我们也将讨论在选择可用的组件实现时保持兼容性的问题。

8.2.1 引导客户端

Bootstrap类负责为客户端和使用无连接协议的应用程序创建Channel,如图8-2所示。

图8-2 引导过程

代码清单8-1中的代码引导了一个使用NIO TCP传输的客户端。

代码清单8-1 引导一个客户端

 EventLoopGroup group = new NioEventLoopGroup; Bootstrap bootstrap = new Bootstrap;   ← --  创建一个Bootstrap类的实例以创建和连接新的客户端Channel bootstrap.group(group)  ← -- 设置EventLoopGroup,提供用于处理Channel事件的EventLoop   .channel(NioSocketChannel.class)   ← --   指定要使用的Channel 实现   .handler(new SimpleChannelInboundHandler<ByteBuf> {  ← --  设置用于Channel 事件和数据的ChannelInboundHandler     @Override     protected void channeRead0(       ChannelHandlerContext channelHandlerContext,       ByteBuf byteBuf) throws Exception {       System.out.println("Received data");     }   } );ChannelFuture future = bootstrap.connect(  new InetSocketAddress("www.manning.com", 80));  ← --   连接到远程主机future.addListener(new ChannelFutureListener {  @Override  public void operationComplete(ChannelFuture channelFuture)    throws Exception {    if (channelFuture.isSuccess) {      System.out.println("Connection established");    } else {      System.err.println("Connection attempt failed");      channelFuture.cause.printStackTrace;    }   }} );  

这个示例使用了前面提到的流式语法;这些方法(除了connect方法以外)将通过每次方法调用所返回的对Bootstrap实例的引用链接在一起。

8.2.2 Channel和EventLoopGroup的兼容性

代码清单8-2所示的目录清单来自io.netty.channel包。你可以从包名以及与其相对应的类名的前缀看到,对于NIO以及OIO传输两者来说,都有相关的EventLoopGroupChannel实现。

代码清单8-2 相互兼容的EventLoopGroupChannel

channel├───nio│     NioEventLoopGroup├───oio│     OioEventLoopGroup└───socket   ├───nio   │     NioDatagramChannel   │     NioServerSocketChannel   │     NioSocketChannel   └───oio        OioDatagramChannel        OioServerSocketChannel        OioSocketChannel  

必须保持这种兼容性,不能混用具有不同前缀的组件,如NioEventLoopGroupOioSocketChannel。代码清单8-3展示了试图这样做的一个例子。

代码清单8-3 不兼容的ChannelEventLoopGroup

EventLoopGroup group = new NioEventLoopGroup;Bootstrap bootstrap = new Bootstrap;   ← --  创建一个新的Bootstrap类的实例,以创建新的客户端Channelbootstrap.group(group)  ← --  指定一个适用于NIO 的EventLoopGroup 实现  .channel(OioSocketChannel.class)  ← --   指定一个适用于OIO 的Channel实现类  .handler(new SimpleChannelInboundHandler<ByteBuf> {  ← --  设置一个用于处理Channel的I/O 事件和数据的ChannelInboundHandler     @Override     protected void channelRead0(       ChannelHandlerContext channelHandlerContext,       ByteBuf byteBuf) throws Exception {       System.out.println("Received data");     }  } );ChannelFuture future = bootstrap.connect(  new InetSocketAddress("www.manning.com", 80));  ← --  尝试连接到远程节点 future.syncUninterruptibly;  

这段代码将会导致IllegalStateException,因为它混用了不兼容的传输。

Exception in thread "main" java.lang.IllegalStateException:incompatible event loop type: io.netty.channel.nio.NioEventLoop atio.netty.channel.AbstractChannel$AbstractUnsafe.register(AbstractChannel.java:571)  

关于IllegalStateException的更多讨论

在引导的过程中,在调用bind或者connect方法之前,必须调用以下方法来设置所需的组件:

  • group;

  • channel或者channelFactory;

  • handler。

如果不这样做,则将会导致IllegalStateException。对handler方法的调用尤其重要,因为它需要配置好ChannelPipeline

8.3 引导服务器

我们将从ServerBootstrap API的概要视图开始我们对服务器引导过程的概述。然后,我们将会探讨引导服务器过程中所涉及的几个步骤,以及几个相关的主题,包含从一个ServerChannel的子Channel中引导一个客户端这样的特殊情况。

8.3.1 ServerBootstrap类

表8-2列出了ServerBootstrap类的方法。

表8-2 ServerBootstrap类的方法

名  称

描  述

group

设置ServerBootstrap要用的EventLoopGroup。这个EventLoopGroup将用于ServerChannel和被接受的子Channel的I/O处理

channel

设置将要被实例化的ServerChannel

channelFactory

如果不能通过默认的构造函数[8]创建Channel,那么可以提供一个Channel- Factory

localAddress

指定ServerChannel应该绑定到的本地地址。如果没有指定,则将由操作系统使用一个随机地址。或者,可以通过bind方法来指定该localAddress

option

指定要应用到新创建的ServerChannelChannelConfigChannel- Option。这些选项将会通过bind方法设置到Channel。在bind方法被调用之后,设置或者改变ChannelOption都不会有任何的效果。所支持的ChannelOption取决于所使用的Channel类型。参见正在使用的ChannelConfig的API文档

childOption

指定当子Channel被接受时,应用到子ChannelChannelConfigChannelOption。所支持的ChannelOption取决于所使用的Channel的类型。参见正在使用的ChannelConfig的API文档

attr

指定ServerChannel上的属性,属性将会通过bind方法设置给Channel。在调用bind方法之后改变它们将不会有任何的效果

childAttr

将属性设置给已经被接受的子Channel。接下来的调用将不会有任何的效果

handler

设置被添加到ServerChannelChannelPipeline中的ChannelHandler。更加常用的方法参见childHandler

childHandler

设置将被添加到已被接受的子ChannelChannelPipeline中的Channel- Handlerhandler方法和childHandler方法之间的区别是:前者所添加的ChannelHandler由接受子ChannelServerChannel处理,而childHandler方法所添加的ChannelHandler将由已被接受的子Channel处理,其代表一个绑定到远程节点的套接字

clone

克隆一个设置和原始的ServerBootstrap相同的ServerBootstrap

bind

绑定ServerChannel并且返回一个ChannelFuture,其将会在绑定操作完成后收到通知(带着成功或者失败的结果)

下一节将介绍服务器引导的详细过程。

8.3.2 引导服务器

你可能已经注意到了,表8-2中列出了一些在表8-1中不存在的方法:childHandlerchildAttrchildOption。这些调用支持特别用于服务器应用程序的操作。具体来说,ServerChannel的实现负责创建子Channel,这些子Channel代表了已被接受的连接。因此,负责引导ServerChannelServerBootstrap提供了这些方法,以简化将设置应用到已被接受的子ChannelChannelConfig的任务。

图8-3展示了ServerBootstrapbind方法被调用时创建了一个ServerChannel,并且该ServerChannel管理了多个子Channel

图8-3 ServerBootstrapServerChannel

代码清单8-4中的代码实现了图8-3中所展示的服务器的引导过程。

代码清单8-4 引导服务器

NioEventLoopGroup group = new NioEventLoopGroup;ServerBootstrap bootstrap = new ServerBootstrap;   ← --  创建ServerBootstrapbootstrap.group(group)  ← --  设置EventLoopGroup,其提供了用于处理Channel 事件的EventLoop  .channel(NioServerSocketChannel.class)  ← --  指定要使用的Channel 实现   .childHandler(new SimpleChannelInboundHandler<ByteBuf> {   ← --  设 置用于处理已被接受的子Channel的I/O及数据的ChannelInbound-Handler    @Override    protected void channelRead0(ChannelHandlerContext ctx,      ByteBuf byteBuf) throws Exception {      System.out.println("Received data");    }  } );ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));    ← -- 通过配置好的ServerBootstrap的实例绑定该Channelfuture.addListener(new ChannelFutureListener {  @Override  public void operationComplete(ChannelFuture channelFuture)    throws Exception {    if (channelFuture.isSuccess) {      System.out.println("Server bound");    } else {      System.err.println("Bound attempt failed");      channelFuture.cause.printStackTrace;    }  }} ); 

8.4 从Channel引导客户端

假设你的服务器正在处理一个客户端的请求,这个请求需要它充当第三方系统的客户端。当一个应用程序(如一个代理服务器)必须要和组织现有的系统(如Web服务或者数据库)集成时,就可能发生这种情况。在这种情况下,将需要从已经被接受的子Channel中引导一个客户端Channel

你可以按照8.2.1节中所描述的方式创建新的Bootstrap实例,但是这并不是最高效的解决方案,因为它将要求你为每个新创建的客户端Channel定义另一个EventLoop。这会产生额外的线程,以及在已被接受的子Channel和客户端Channel之间交换数据时不可避免的上下文切换。

一个更好的解决方案是:通过将已被接受的子ChannelEventLoop传递给Bootstrapgroup方法来共享该EventLoop。因为分配给EventLoop的所有Channel都使用同一个线程,所以这避免了额外的线程创建,以及前面所提到的相关的上下文切换。这个共享的解决方案如图8-4所示。

图8-4 在两个Channel之间共享EventLoop

实现EventLoop共享涉及通过调用group方法来设置EventLoop,如代码清单8-5所示。

代码清单8-5 引导服务器

ServerBootstrap bootstrap = new ServerBootstrap;   ← --  创建ServerBootstrap 以创建ServerSocketChannel,并绑定它bootstrap.group(new NioEventLoopGroup, new NioEventLoopGroup)  ← --  设置EventLoopGroup,其将提供用以处理Channel 事件的EventLoop  .channel(NioServerSocketChannel.class)  ← --  指定要使用的Channel 实现   .childHandler(  ← --  设置用于处理已被接受的子Channel 的I/O 和数据的ChannelInboundHandler     new SimpleChannelInboundHandler<ByteBuf> {      ChannelFuture connectFuture;      @Override      public void channelActive(ChannelHandlerContext ctx)        throws Exception {        Bootstrap bootstrap = new Bootstrap;  ← --  创建一个Bootstrap类的实例以连接到远程主机        bootstrap.channel(NioSocketChannel.class).handler(  ← --  指定Channel的实现           new SimpleChannelInboundHandler<ByteBuf> {   ← -- 为入站I/O 设置ChannelInboundHandler            @Override            protected void channelRead0(              ChannelHandlerContext ctx, ByteBuf in)              throws Exception {              System.out.println("Received data");            }          } );        bootstrap.group(ctx.channel.eventLoop);  ← --  使用与分配给已被接受的子Channel 相同的EventLoop        connectFuture = bootstrap.connect(          new InetSocketAddress("www.manning.com", 80));   ← --  连接到远程节点      }      @Override      protected void channelRead0(        ChannelHandlerContext channelHandlerContext,          ByteBuf byteBuf) throws Exception {        if (connectFuture.isDone) {          // do something with the data  ← --  当连接完成时,执行一些数据操作(如代理)        }      }    } );ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));  ← --  通过配置好的ServerBootstrap绑定该Server-SocketChannelfuture.addListener(new ChannelFutureListener {  @Override  public void operationComplete(ChannelFuture channelFuture)    throws Exception {    if (channelFuture.isSuccess) {      System.out.println("Server bound");    } else {      System.err.println("Bind attempt failed");      channelFuture.cause.printStackTrace;    }  }} );  

我们在这一节中所讨论的主题以及所提出的解决方案都反映了编写Netty应用程序的一个一般准则:尽可能地重用EventLoop,以减少线程创建所带来的开销。

8.5 在引导过程中添加多个ChannelHandler

在所有我们展示过的代码示例中,我们都在引导的过程中调用了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中的简便方法。你只需要简单地向BootstrapServerBootstrap的实例提供你的Channel-Initializer实现即可,并且一旦Channel被注册到了它的EventLoop之后,就会调用你的initChannel版本。在该方法返回之后,ChannelInitializer的实例将会从Channel-Pipeline中移除它自己。

代码清单8-6定义了ChannelInitializerImpl类,并通过ServerBootstrapchildHandler方法注册它[9]。你可以看到,这个看似复杂的操作实际上是相当简单直接的。

代码清单8-6 引导和使用ChannelInitializer

ServerBootstrap bootstrap = new ServerBootstrap;  ← --  创建ServerBootstrap 以创建和绑定新的Channel bootstrap.group(new NioEventLoopGroup, new NioEventLoopGroup)  ← --  设置EventLoopGroup,其将提供用以处理Channel 事件的EventLoop  .channel(NioServerSocketChannel.class)  ← --   指定Channel 的实现  .childHandler(new ChannelInitializerImpl);   ← -- 注册一个ChannelInitializerImpl 的实例来设置ChannelPipeline  ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));  ← -- 绑定到地址future.sync;final class ChannelInitializerImpl extends ChannelInitializer {[10]  ← -- 用以设置ChannelPipeline 的自定义ChannelInitializerImpl 实现  @Override  protected void initChannel(Channel ch) throws Exception { ← -- 将所需的ChannelHandler添加到ChannelPipeline    ChannelPipeline pipeline = ch.pipeline;    pipeline.addLast(new HttpClientCodec);    pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));  }}  

如果你的应用程序使用了多个ChannelHandler,请定义你自己的ChannelInitializer实现来将它们安装到ChannelPipeline中。

8.6 使用Netty的ChannelOption和属性

在每个Channel创建时都手动配置它可能会变得相当乏味。幸运的是,你不必这样做。相反,你可以使用option方法来将ChannelOption应用到引导。你所提供的值将会被自动应用到引导所创建的所有Channel。可用的ChannelOption包括了底层连接的详细信息,如keep-alive或者超时属性以及缓冲区设置。

Netty应用程序通常与组织的专有软件集成在一起,而像Channel这样的组件可能甚至会在正常的Netty生命周期之外被使用。在某些常用的属性和数据不可用时,Netty提供了AttributeMap抽象(一个由Channel引导类提供的集合)以及AttributeKey<T>(一个用于插入和获取属性值的泛型类)。使用这些工具,便可以安全地将任何类型的数据项与客户端和服务器Channel(包含ServerChannel的子Channel)相关联了。

例如,考虑一个用于跟踪用户和Channel之间的关系的服务器应用程序。这可以通过将用户的ID存储为Channel的一个属性来完成。类似的技术可以被用来基于用户的ID将消息路由给用户,或者关闭活动较少的Channel

代码清单8-7展示了可以如何使用ChannelOption来配置Channel,以及如果使用属性来存储整型值。

代码清单8-7 使用属性值

final AttributeKey<Integer> id = AttributeKey.newInstance("ID"); [11]  ← --  创建一个AttributeKey以标识该属性Bootstrap bootstrap = new Bootstrap;  ← --  创建一个Bootstrap 类的实例以创建客户端Channel 并连接它们bootstrap.group(new NioEventLoopGroup)   ← --  设置EventLoopGroup,其提供了用以处理Channel事件的EventLoop.channel(NioSocketChannel.class)  ← --  指定Channel的实现.handler(  new SimpleChannelInboundHandler<ByteBuf> {  ← -- 设置用以处理Channel 的I/O 以及数据的Channel-InboundHandler     @Override    public void channelRegistered(ChannelHandlerContext ctx)      throws Exception {      Integer idValue = ctx.channel.attr(id).get;  ← -- 使用AttributeKey 检索属性以及它的值      // do something with the idValue    }    @Override    protected void channelRead0(      ChannelHandlerContext channelHandlerContext,      ByteBuf byteBuf) throws Exception {      System.out.println("Received data");    }  });bootstrap.option(ChannelOption.SO_KEEPALIVE,true)  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);  ← --  设置ChannelOption,其将在connect或者bind方法被调用时被设置到已经创建的Channel 上bootstrap.attr(id, 123456);  ← --  存储该id 属性 ChannelFuture future = bootstrap.connect(  new InetSocketAddress("www.manning.com", 80));  ← --  使用配置好的Bootstrap实例连接到远程主机future.syncUninterruptibly;  

8.7 引导DatagramChannel

前面的引导代码示例使用的都是基于TCP协议的SocketChannel,但是Bootstrap类也可以被用于无连接的协议。为此,Netty提供了各种DatagramChannel的实现。唯一区别就是,不再调用connect方法,而是只调用bind方法,如代码清单8-8所示。

代码清单8-8 使用BootstrapDatagramChannel

Bootstrap bootstrap = new Bootstrap;  ← --   创建一个Bootstrap 的实例以创建和绑定新的数据报Channelbootstrap.group(new OioEventLoopGroup).channel(  ← -- 设置EventLoopGroup,其提供了用以处理Channel 事件的EventLoop   OioDatagramChannel.class).handler(  ← -- 指定Channel的实现  new SimpleChannelInboundHandler<DatagramPacket>{  ← -- 设置用以处理Channel 的I/O 以及数据的Channel-InboundHandler    @Override    public void channelRead0(ChannelHandlerContext ctx,      DatagramPacket msg) throws Exception {      // Do something with the packet    }  });ChannelFuture future = bootstrap.bind(new InetSocketAddress(0));   ← -- 调用bind方法,因为该协议是无连接的future.addListener(new ChannelFutureListener {  @Override  public void operationComplete(ChannelFuture channelFuture)    throws Exception {    if (channelFuture.isSuccess) {      System.out.println("Channel bound");    } else {      System.err.println("Bind attempt failed");      channelFuture.cause.printStackTrace;    }  }});  

8.8 关闭

引导使你的应用程序启动并且运行起来,但是迟早你都需要优雅地将它关闭。当然,你也可以让JVM在退出时处理好一切,但是这不符合优雅的定义,优雅是指干净地释放资源。关闭Netty应用程序并没有太多的魔法,但是还是有些事情需要记在心上。

最重要的是,你需要关闭EventLoopGroup,它将处理任何挂起的事件和任务,并且随后释放所有活动的线程。这就是调用EventLoopGroup.shutdownGracefully方法的作用。这个方法调用将会返回一个Future,这个Future将在关闭完成时接收到通知。需要注意的是,shutdownGracefully方法也是一个异步的操作,所以你需要阻塞等待直到它完成,或者向所返回的Future注册一个监听器以在关闭完成时获得通知。

代码清单8-9符合优雅关闭的定义。

代码清单8-9 优雅关闭

EventLoopGroup group = new NioEventLoopGroup;  ← --  创建处理I/O 的EventLoopGroupBootstrap bootstrap = new Bootstrap;  ← --  创建一个Bootstrap类的实例并配置它bootstrap.group(group)  .channel(NioSocketChannel.class);...Future<?> future = group.shutdownGracefully;  ← --  shutdownGracefully方法将释放所有的资源,并且关闭所有的当前正在使用中的Channel// block until the group has shutdownfuture.syncUninterruptibly;  

或者,你也可以在调用EventLoopGroup.shutdownGracefully方法之前,显式地在所有活动的Channel上调用Channel.close方法。但是在任何情况下,都请记得关闭EventLoopGroup本身。

8.9 小结

在本章中,你学习了如何引导Netty服务器和客户端应用程序,包括那些使用无连接协议的应用程序。我们也涵盖了一些特殊情况,包括在服务器应用程序中引导客户端Channel,以及使用ChannelInitializer来处理引导过程中的多个ChannelHandler的安装。你看到了如何设置Channel的配置选项,以及如何使用属性来将信息附加到Channel。最后,你学习了如何优雅地关闭应用程序,以有序地释放所有的资源。

在下一章中,我们将研究Netty提供的帮助你测试你的ChannelHandler实现的工具。


[1] Channel继承了AttributeMap。——译者注

[2] 分层抽象。——译者注

[3] 应用程序的逻辑或实现。——译者注

[4] “拼图”指的是Netty的核心概念以及组件,也包括了如何完整正确地组织并且运行一个Netty应用程序。——译者注

[5] Java平台,标准版第8版API规范,java.lang,Interface Cloneable:http://docs.oracle.com/javase/8/docs/api/ java/lang/Cloneable.html。

[6] 被浅拷贝的EventLoopGroup。——译者注

[7] 这里指默认的无参构造函数,因为内部使用了反射来实现Channel的创建。——译者注

[8] 这里指无参数的构造函数。——译者注

[9] 注册到ServerChannel的子ChannelChannelPipeline。——译者注

[10] 在大部分的场景下,如果你不需要使用只存在于SocketChannel上的方法,使用ChannelInitializer- 就可以了,否则你可以使用ChannelInitializer,其中SocketChannel扩展了Channel。——译者注

[11] 需要注意的是,AttributeKey上同时存在newInstance(String)valueOf(String)方法,它们都可以用来获取具有指定名称的AttributeKey实例,不同的是,前者可能会在多线程环境下使用时抛出异常(实际上调用了createOrThrow(String)方法)——通常适用于初始化静态变量的时候;而后者(实际上调用了getOrCreate(String)方法)则更加通用(线程安全)。——译者注