【Netty】Netty之Bootstrapping

1、前言bootstrap

  前面已經學習了Netty的EventLoop以及線程模型,接着學習Netty的Bootstrapping。服務器

2、Bootstrapping網絡

  在學習了Netty中的不少組件後,如何將這些組件有效的組合至應用程序中,這須要使用應用引導程序,引導應用程序是將其配置爲運行的過程,Netty以一種絕對應用程序的方式處理引導。app

  2.1 Bootstrap類異步

  Bootstrap類的繼承結構圖以下圖所示。ide

  

  一個服務器使用一個父通道來接受來自客戶端的鏈接並建立子通道來與它們進行通訊,而一個客戶端極可能只須要一個非父通道來進行全部的網絡交互,而前面討論的組件會參與引導過程,有些在客戶端和服務端都會被使用。兩個應用程序類型共同的引導步驟由AbstractBootstrap處理,而特定於客戶端或服務器的引導步驟分別由Bootstrap或ServerBootstrap處理。oop

  爲什麼Bootstrap爲Cloneable?由於有時須要建立具備相似或相同設置的多個通道,爲了支持此模式,不須要爲每一個通道建立和配置新的引導實例,所以將AbstractBootstrap標記爲Cloneable,在已配置的引導程序上調用clone()方法將返回可當即使用的另外一個引導實例,這只會建立引導程序的EventLoopGroup的淺層副本,因此其被全部克隆的通道共享學習

  2.2 引導客戶端和無鏈接協議spa

  Bootstrap在客戶端或無鏈接協議的應用程序中使用。.net

  1. 引導客戶端

  Bootstrap類負責爲客戶端和使用無鏈接協議的應用程序建立通道,以下圖所示。

  

  如下代碼引導使用NIO TCP傳輸的客戶端。  

EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
    .channel(NioSocketChannel.class)
    .handler(new SimpleChannelInboundHandler<ByteBuf>() {
        @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();
        }
    }
} );

  2. Channel和EventLoopGroup兼容性

  io.netty.channel包中的類結構以下。

   

       

  能夠看到對於NIO和OIO傳輸,都有相關的EventLoopGroup和Channel實現,不能混合具備不一樣前綴的組件,例如NioEventLoopGroup和OioSocketChannel。以下代碼展現不兼容的使用用法。  

EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
    .channel(OioSocketChannel.class)
    .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.manning.com", 80));
future.syncUninterruptibly();

  其中NIOEventLooop和OioSocketChannel不兼容,將會拋出IllegalStateException異常。

  在調用bind或者connect方法以前,須要調用group、channel或channelFactory、handler方法,不然會拋出IllegalStateException異常。

  2.3 引導服務器

  ServerChannel的實現負責建立接受鏈接的子通道,ServerBootstrap經過bind()方法建立一個ServerChannel,ServerChannel可管理多個子通道,具體以下圖所示。

  

  下面代碼展現瞭如何引導服務器。  

NioEventLoopGroup group = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group)
    .channel(NioServerSocketChannel.class)
    .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx,
            ByteBuf byteBuf) throws Exception {
                System.out.println("Received data");
            }
    } );
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.err.println("Bound attempt failed");
            channelFuture.cause().printStackTrace();
        }
    }
} );

  2.4 由通道引導客戶端

  假設服務器正在處理客戶端請求,並要求服務器做爲第三個系統的客戶端,如代理服務器。此時須要從ServerChannel引導客戶端通道。一個較好的方法是經過Bootstrap類的group方法傳遞Channel對應的EventLoop,由於全部分配給EventLoop的通道都使用相同的線程,這避免了額外的線程建立和相關的上下文切換。具體以下圖所示。

  

  經過group方法共享EventLoop的代碼以下。  

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup())
    .channel(NioServerSocketChannel.class)
    .childHandler(
        new SimpleChannelInboundHandler<ByteBuf>() {
            ChannelFuture connectFuture;
            @Override
            public void channelActive(ChannelHandlerContext ctx)
                throws Exception {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.channel(NioSocketChannel.class).handler(
                    new SimpleChannelInboundHandler<ByteBuf>() {
                    @Override
                    protected void channelRead0(
                        ChannelHandlerContext ctx, ByteBuf in)
                        throws Exception {
                        System.out.println("Received data");
                    }
                } );
            bootstrap.group(ctx.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));
future.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();
        }
    }
} );

  2.5 在引導過程當中添加多個ChannelHandler

  在上面示例中,在引導過程當中經過調用handler或者childHandler方法添加單個ChannelHandler,而且咱們知道在ChannelPipeline中能夠有多個ChannelHandler鏈,可是在引導過程當中只添加了一個ChannelHandler。Netty提供了ChannelInboundHandlerAdapter,其提供了initChannel方法,該方法能夠將多個ChannelHandler添加至ChannelPipeline中,你只須要將ChannelInitializer的實現提供給引導程序,而一旦Channel註冊了EventLoop,那麼initChannel方法將被調用,當方法返回後,ChannelInitializer將自身從ChannelPipeline中移除,以下代碼展現了具體的操做。 

ServerBootstrap bootstrap = new ServerBootstrap();
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)); } }

  2.6 使用Netty的ChannelOptions和attributes

  當建立通道時手動配置很是麻煩,可使用option方法將ChannelOptions提供給引導程序,你提供的值將自動應用於在引導中建立的全部通道。ChannelOptions包括鏈接詳細信息,如保持活動、超時屬性和緩衝區設置等。Netty還可以使用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 {
            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");
        }
    }
);
bootstrap.option(ChannelOption.SO_KEEPALIVE,true)
    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
bootstrap.attr(id, 123456);
ChannelFuture future = bootstrap.connect(
new InetSocketAddress("www.manning.com", 80));
future.syncUninterruptibly();

  2.7 引導DatagramChannels

  前面的示例使用的SocketChannel,是基於TCP協議,可是Bootstrap也能夠用於無鏈接的協議,如UDP協議,惟一的區別在於不調用connect方法,只使用bind方法,具體以下代碼所示。 

Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new OioEventLoopGroup()).channel(
    OioDatagramChannel.class).handler(
    new SimpleChannelInboundHandler<DatagramPacket>(){
        @Override
        public void channelRead0(ChannelHandlerContext ctx,
            DatagramPacket msg) throws Exception {
            // Do something with the packet
        }
    }
);
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.err.println("Bind attempt failed");
            channelFuture.cause().printStackTrace();
        }
    }
})

  2.8 關閉

  引導使得應用運行,可是以後須要優雅的關閉引導。須要關閉EventLoopGroup,可調用EventLoopGroup.shutdownGracefully() 方法,其是異步的,會返回ChannelFuture,在觀泉關閉後會收到通知,下面是使用示例。  

EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
    .channel(NioSocketChannel.class);
...
Future<?> future = group.shutdownGracefully();
// block until the group has shutdown
future.syncUninterruptibly();

  也能夠在調用shutdownGracefully方法以前顯示調用close方法,要讓EventLoopGroup本身主動關閉。

3、總結

  本篇博文講解了Bootstrap,包括客戶端和服務端的引導,以及如何啓動標準的客戶端和服務端程序。也謝謝各位園友的觀看~

相關文章
相關標籤/搜索