Netty实战八之引导

经过前面的学习,咱们可能要考虑一个问题:如何将这些部分组织起来,成为一个可实际运行的应用程序呢?bootstrap

答案是引导。简单来讲,引导一个应用程序是指对它进行配置,并使它运行起来的过程——尽管该过程的具体细节可能并不如它定义那样简单,尤为是对于一个网络应用程序来讲。安全

引导是咱们一直以来都在组装的完整拼图中缺失的那一块。当你把它放到正确的位置上时,你的Netty应用程序就完整了。服务器

一、Bootstarp类网络

引导类的层次结构包括一个抽象的父类和两个具体的引导子类,以下图所示。ide

Netty实战八之引导

相对于将具体的引导类分别看做用于服务器和客户端的引导来讲,记住它们的本意是用来支撑不一样的应用程序的功能的将有所裨益。也就是说,服务器致力于使用一个父Channel来接受来自客户端的链接、并建立子Channel以用于它们之间的通讯;而客户端将最可能只须要一个单独的、没有父Channel的Channel来用于全部的网络交互(这也适用于无链接的传输协议,如UDP,由于它们并非每一个链接都须要一个单独的Channel)工具

两种应用程序类型之间通用的引导步骤由AbstractBootstrap处理,而特定于客户端或者服务器的引导步骤则分别由Bootstrap或ServerBootstrap处理。oop

为何引导类是Cloneable的?学习

你有时可能会须要建立多个具备相似配置或者彻底相同配置的Channel。为了支持这种模式而又不须要为每一个Channel都建立并配置一个新的引导类实例,AbstractBootstrap被标记为了Cloneable。在一个已经配置完成的引导类实例上调用clone()方法将返回另外一个能够当即使用的引导类实例。.net

注意,这种方式只会建立引导类实例的EventLoopGroup的一个浅拷贝,因此,后者将在全部克隆的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>

二、引导客户端和无链接协议

Bootstrap类被用于客户端或者使用了无链接协议的应用程序中。Bootstrap类负责为客户端和使用无链接协议的应用程序建立Channel,以下图所示。
Netty实战八之引导
如下代码引导了一个使用NIO TCP传输的客户端

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

这个示例使用了前面提到的流式语法,这些方法将经过每次方法调用所返回的对Bootstrap实例的引用连接在一块儿。

三、Channel和EventLoopGroup的兼容性

EventLoopGroup group = new NioEventLoopGroup(); //建立一个Bootstrap类的实例以建立和链接新的客户端Channel
Bootstrap bootstrap = new Bootstrap(); //指定一个适用于OIO的Channel实现类
bootstrap.group(group)
.channel(OioSocketChannel.class) //设置一个用于处理Channel的I/O事件和数据的ChannelInboundHandler
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
System.out.println("Received data");
}
});
ChannelFuture future = bootstrap.connect( //尝试链接到远程节点
new InetSocketAddress("www.myself.com",80)
);
future.syncUninterruptibly();
这段代码将会致使IllegalStateException,由于它混用了不兼容的传输。

关于IllegalStateException的更多讨论

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

——group()

——channel()或者channelFactory()

——handler()

若是不这样作,则将会致使IllegalStateException。对handler()方法的调用尤为重要,由于它须要配置好ChannelPipeline。

四、引导服务器

具体来讲,ServerChannel的实现负责建立子Channel,这些子Channel表明了已被接受的链接。所以,负责引导ServerChannel的ServerBootstrap提供了这些方法,以简化将设置应用到已被接受的子Channel的ChannelConfig的任务。

下图展现了ServerBootstrap在bind()方法被调用时建立了一个ServerChannel,而且该ServerChannel管理多个子Channel。

Netty实战八之引导
如下代码实现了服务器的引导过程。

NioEventLoopGroup group = new NioEventLoopGroup();        //建立ServerBootstrap
        ServerBootstrap bootstrap = new ServerBootstrap();        //设置EventLoopGroup,其提供了用于处理Channel事件的EventLoop
        bootstrap.group(group)                //指定要使用的Channel实现
                .channel(NioServerSocketChannel.class)
                .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
                    @Override                    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf)                            throws Exception {
                        System.out.println("Received data");
                    }
                });        //经过配置好的ServerBootstrap的实例绑定该Channel
        ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
        future.addListener(new ChannelFutureListener() {
            @Override            public void operationComplete(ChannelFuture channelFuture) throws Exception {                if (channelFuture.isSuccess()){
                    System.out.println("Server bound");
                } else {
                    System.out.println("Bound attempt failed");
                    channelFuture.cause().printStackTrace();
                }
            }
        });

五、从Channel引导客户端

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

经过将已被接受的子Channel的EventLoop传递给Bootstrap的group()方法来共享该EventLoop。由于分配给EventLoop的全部Channel都使用同一个线程,因此这避免了额外的线程建立,以及前面所提到的相关的上下文切换。这个共享的解决方案以下图所示。
Netty实战八之引导

实现EventLoop共享涉及经过调用group()方法来设置EventLoop,以下代码所示。

//建立ServerBootstrap以建立和绑定新的Channel
        ServerBootstrap bootstrap = new ServerBootstrap();        //设置EventLoopGroup,其将提供用以处理Channel事件的EventLoop
        bootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup())                //指定Channel的实现
                .channel(NioServerSocketChannel.class)                //注册一个ChannelInitializerImpl的实例来设置ChannelPipeline
                .childHandler(new ChannelInitializerImpl());
        ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
        future.sync();*/

        /*//建立一个AttributeKey以标识该属性
        final AttributeKey<Integer> id = AttributeKey.valueOf("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 channelHandlerContext, ByteBuf byteBuf)
                            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("www.myself.com",80));
        future.syncUninterruptibly();

以上示例都反应了编写Netty应用程序的一个通常准则:尽量地重用EventLoop,以减小线程建立带来的开销。

六、在引导过程当中添加多个ChannelHandler

在全部咱们展现过的代码示例中,咱们都在引导的过程当中调用了handler()或者childHandler()方法来添加单个的ChannelHandler。这对于简单的应用程序来讲可能已经足够了,可是它不能知足更加复杂的需求。例如,一个必需要支持多种协议的应用程序将会有不少的ChannelHandler,而不会是一个庞大而又笨重的类。

正如你常常看到的同样,你能够根据须要,经过在ChannelPipeline中将它们连接在一块儿来部署尽量多的ChannelHandler。可是,若是在引导的过程当中你只能设置一个ChannelHandler,那么你应该怎么作到这一点呢?

正是针对于这个用例,Netty提供了一个特殊的ChannelInboundHandlerAdapter子类:

public abstract class ChannelInitializer<C extend Channel> extends ChannInboundHandlerAdapter

它定义了下面的方法:

protected abstract void initChannel(C ch) throws Exception

这个方法提供了一种将多个ChannelHandler添加到一个ChannelPipeline中的简便方法。你只须要简单地向Bootstrap或ServerBootstrap的实例提供你的ChannelInitializer实现便可,而且一旦Channel被注册到了它的EventLoop以后,就会调用你的initChannel()版本。在该方法返回以后,ChannelInitializer的实例将会从ChannelPipeline中移除它本身。

如下代码定义了ChannelInitializer类,并经过ServerBootstrap的childHandler()方法注册了它。

/建立ServerBootstrap以建立和绑定新的Channel
        ServerBootstrap bootstrap = new ServerBootstrap();        //设置EventLoopGroup,其将提供用以处理Channel事件的EventLoop
        bootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup())                //指定Channel的实现
                .channel(NioServerSocketChannel.class)                //注册一个ChannelInitializerImpl的实例来设置ChannelPipeline
                .childHandler(new ChannelInitializerImpl());        ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
        future.sync();//用以设置ChannelPipeline的自定义ChannelInitializerImpl实现
    final static class ChannelInitializerImpl extends ChannelInitializer<Channel>{
        @Override
        protected void initChannel(Channel channel) throws Exception {            ChannelPipeline pipeline = channel.pipeline();
            pipeline.addLast(new HttpClientCodec());
            pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
        }
    }

七、使用Netty的ChannelOption和属性

在每一个Channel建立时都手动配置它可能会变得至关乏味。幸运的是,你没必要这样作。相反,你可使用option()方法来将ChannelOption应用到引导。你所提供的值将会被自动应用到引导所建立的全部Channel。可用的ChannelOption包括了底层链接的详细信息,如keep-aliva或者超时属性以及缓冲区设置。

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

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

如下代码展现了能够如何使用ChannelOption来配置Channel,以及若是使用属性来存储整型值。

//建立一个AttributeKey以标识该属性
        final AttributeKey<Integer> id = AttributeKey.valueOf("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 channelHandlerContext, ByteBuf byteBuf)
                            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("www.myself.com",80));
        future.syncUninterruptibly();

八、引导DatagramChannel

前面的引导代码示例使用的都是基于TCP协议的SocketChannel,可是Bootstrap类也能够被用于无链接的协议。为此,Netty提供了各类DatagramChannel的实现。惟一区别就是,再也不调用connect()方法,而是只调用bind(),以下代码所示。

//建立一个Bootstrap的实例以建立和绑定新的数据报Channel
        Bootstrap bootstrap = new Bootstrap();        //设置EventLoopGroup,其提供了用以处理Channel事件的EventLoop
        bootstrap.group(new OioEventLoopGroup()).channel(OioDatagramChannel.class).handler(                new SimpleChannelInboundHandler<DatagramPacket>() {                    @Override
                    protected void channelRead0(ChannelHandlerContext channelHandlerContext,
                                                DatagramPacket datagramPacket) throws Exception {                        //Do something with the packet
                    }
                }
        );        //调用bind()方法,由于该协议是无链接的
        ChannelFuture future = bootstrap.bind(new InetSocketAddress(0));
        future.addListener(new ChannelFutureListener() {            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {                if (channelFuture.isSuccess()){
                    System.out.println("Channel bound");
                } else {
                    System.out.println("Bind attempt faild");
                    channelFuture.cause().printStackTrace();
                }
            }
        });

九、关闭

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

最重要的是,你须要关闭EventLoopGroup,它将处理任何挂起的事件和任务,而且随后释放全部活动的线程。这就是调用EventLoopGroup.shutdownGracefully()方法的做用。这个方法调用将会返回一个Future,这个Future将在关闭完成时接收到通知。须要注意的是,所返回的Future注册一个监听器以在关闭完成时得到通知。

EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class);
        ...        //shutdownGracefully()方法将释放全部的资源,而且关闭全部的当前正在使用中的Channel
        io.netty.util.concurrent.Future future = group.shutdownGracefully();        //block until the group has shutdown
        future.syncUninterruptibly();

或者,你也能够调用EventLoopGroup.shutdownGracefully()方法以前,显式地在全部活动的Channel上调用Channel.close()方法。可是在任何状况下,都请记得关闭EventLoopGroup自己。

相关文章
相关标签/搜索