Netty實戰八之引導

經過前面的學習,咱們可能要考慮一個問題:如何將這些部分組織起來,成爲一個可實際運行的應用程序呢?bootstrap

答案是引導。簡單來講,引導一個應用程序是指對它進行配置,並使它運行起來的過程——儘管該過程的具體細節可能並不如它定義那樣簡單,尤爲是對於一個網絡應用程序來講。安全

引導是咱們一直以來都在組裝的完整拼圖中缺失的那一塊。當你把它放到正確的位置上時,你的Netty應用程序就完整了。服務器

一、Bootstarp類markdown

引導類的層次結構包括一個抽象的父類和兩個具體的引導子類,以下圖所示。網絡

Netty實戰八之引導

相對於將具體的引導類分別看做用於服務器和客戶端的引導來講,記住它們的本意是用來支撐不一樣的應用程序的功能的將有所裨益。也就是說,服務器致力於使用一個父Channel來接受來自客戶端的鏈接、並建立子Channel以用於它們之間的通訊;而客戶端將最可能只須要一個單獨的、沒有父Channel的Channel來用於全部的網絡交互(這也適用於無鏈接的傳輸協議,如UDP,由於它們並非每一個鏈接都須要一個單獨的Channel)ide

兩種應用程序類型之間通用的引導步驟由AbstractBootstrap處理,而特定於客戶端或者服務器的引導步驟則分別由Bootstrap或ServerBootstrap處理。工具

爲何引導類是Cloneable的?oop

你有時可能會須要建立多個具備相似配置或者徹底相同配置的Channel。爲了支持這種模式而又不須要爲每一個Channel都建立並配置一個新的引導類實例,AbstractBootstrap被標記爲了Cloneable。在一個已經配置完成的引導類實例上調用clone()方法將返回另外一個能夠當即使用的引導類實例。學習

注意,這種方式只會建立引導類實例的EventLoopGroup的一個淺拷貝,因此,後者將在全部克隆的Channel實例之間共享。這是能夠接受的,由於一般這些克隆的Channel的生命週期都很短暫,一個典型的場景是——建立一個Channel以進行一次HTTP請求。.net

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自己。

相關文章
相關標籤/搜索