本章介紹java
引導客戶端和服務器web
從Channel引導客戶端數據庫
添加多個ChannelHandlerbootstrap
使用通道選項和屬性api
上一章學習了編寫本身的ChannelHandler和編解碼器並將它們添加到Channel的ChannelPipeline中。本章將講解如何將它們結合在一塊兒使用。安全
Netty提供了簡單統一的方法來引導服務器和客戶端。引導是配置Netty服務器和客戶端程序的一個過程,Bootstrap容許這些應用程序很容易的重複使用。Netty程序的客戶端和服務器均可以使用Bootstrap,其目的是簡化編碼過程,Bootstrap還提供了一個機制就是讓一些組件(channels,pipeline,handlers等等)均可以在後臺工做。服務器
本章將具體結合如下部分一塊兒使用開發Netty程序:socket
EventLoopGroupide
Channeloop
設置ChannelOption
Channel被註冊後將調用ChannelHandler
添加指定的屬性到Channel
設置本地和遠程地址
綁定、鏈接(取決於類型)
9.1 不一樣的引導類型
Netty包含了2個不一樣類型的引導,
第一個是使用服務器的ServerBootstrap,用來接受客戶端鏈接以及爲已接受的鏈接建立子通道;
第二個是用於客戶端的Bootstrap,不接受新的鏈接,而且是在父通道類完成一些操做。
還有一種狀況是處理DatagramChannel實例,這些用於UDP協議,是無鏈接的。換句話說,因爲UDP的性質,因此當處理UDP數據時沒有必要每一個鏈接通道與TCP鏈接同樣。由於通道不須要鏈接後才能發送數據,UDP是無鏈接協議。一個通道能夠處理全部的數據而不須要依賴子通道。
下圖是引導的類關係圖:
爲了對客戶端和服務器之間的關係提供一個共同點,Netty使用AbstractBootstrap類。
經過一個共同的父類,在本章中討論的客戶端和服務器的引導程序可以重複使用通用功能,而無需複製代碼或邏輯。一般狀況下,多個通道使用相同或很是相似的設置時有必要的。而不是爲每個通道建立一個新的引導,Netty使得AbstractBootstrap可複製。
也就是說克隆一個已配置的引導,其返回的是一個可重用而無需配置的引導。Netty的克隆操做只能淺拷貝引導的EventLoopGroup,也就是說EventLoopGroup在全部的克隆的通道中是共享的。這是一個好事情,克隆的通道通常是短暫的,例如一個通道建立一個HTTP請求。
9.2 引導客戶端和無鏈接協議
當須要引導客戶端或一些無鏈接協議時,須要使用Bootstrap類。
建立Bootstrap實例使用new關鍵字,下面是Bootstrap的方法:
group(...),設置EventLoopGroup,EventLoopGroup用來處理全部通道的IO事件
channel(...),設置通道類型
channelFactory(...),使用ChannelFactory來設置通道類型
localAddress(...),設置本地地址,也能夠經過bind(...)或connect(...)
option(ChannelOption<T>, T),設置通道選項,若使用null,則刪除上一個設置的ChannelOption
attr(AttributeKey<T>, T),設置屬性到Channel,若值爲null,則指定鍵的屬性被刪除
handler(ChannelHandler),設置ChannelHandler用於處理請求事件
clone(),深度複製Bootstrap,Bootstrap的配置相同
remoteAddress(...),設置鏈接地址
connect(...),鏈接遠程通道
bind(...),建立一個新的Channel並綁定
9.2.2 怎麼引導客戶端
引導負責客戶端通道鏈接或斷開鏈接,所以它將在調用bind(...)或connect(...)後建立通道。下圖顯示瞭如何工做:
下面代碼顯示了引導客戶端使用NIO TCP傳輸:
package netty.in.action; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; /** * 引導配置客戶端 */ public class BootstrapingClient { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class).handler(new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("Received data"); msg.clear(); } }); ChannelFuture f = b.connect("127.0.0.1", 2048); f.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { System.out.println("connection finished"); } else { System.out.println("connection failed"); future.cause().printStackTrace(); } } }); } }
9.2.3 選擇兼容通道實現
Channel的實現和EventLoop的處理過程在EventLoopGroup中必須兼容,哪些Channel是和EventLoopGroup是兼容的能夠查看API文檔。
經驗顯示,相兼容的實現通常在同一個包下面,例如使用NioEventLoop,NioEventLoopGroup和NioServerSocketChannel在一塊兒。請注意,這些都是前綴「Nio」,而後不會用這些代替另外一個實現和另外一個前綴,如「Oio」,也就是說OioEventLoopGroup和NioServerSocketChannel是不相容的。
Channel和EventLoopGroup的EventLoop必須相容,例如NioEventLoop、NioEventLoopGroup、NioServerSocketChannel是相容的,可是OioEventLoopGroup和NioServerSocketChannel是不相容的。
從類名能夠看出前綴是「Nio」的只能和「Nio」的一塊兒使用,「Oio」前綴的只能和Oio*一塊兒使用,將不相容的一塊兒使用會致使錯誤異常,如OioSocketChannel和NioEventLoopGroup一塊兒使用時會拋出異常:Exception in thread "main" java.lang.IllegalStateException: incompatible event loop type。
9.3 使用ServerBootstrap引導服務器
9.3.1 引導服務器的方法
先看看ServerBootstrap提供了哪些方法
group(...),設置EventLoopGroup事件循環組
channel(...),設置通道類型
channelFactory(...),使用ChannelFactory來設置通道類型
localAddress(...),設置本地地址,也能夠經過bind(...)或connect(...)
option(ChannelOption<T>, T),設置通道選項,若使用null,則刪除上一個設置的ChannelOption
childOption(ChannelOption<T>, T),設置子通道選項
attr(AttributeKey<T>, T),設置屬性到Channel,若值爲null,則指定鍵的屬性被刪除
childAttr(AttributeKey<T>, T),設置子通道屬性
handler(ChannelHandler),設置ChannelHandler用於處理請求事件
childHandler(ChannelHandler),設置子ChannelHandler
clone(),深度複製ServerBootstrap,且配置相同
bind(...),建立一個新的Channel並綁定
9.3.2 怎麼引導服務器
下圖顯示ServerBootstrap管理子通道:
child*方法是在子Channel上操做,經過ServerChannel來管理。
下面代碼顯示使用ServerBootstrap引導配置服務器:
package netty.in.action; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; /** * 引導服務器配置 */ public class BootstrapingServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .childHandler(new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("Received data"); msg.clear(); } }); ChannelFuture f = b.bind(2048); f.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { System.out.println("Server bound"); } else { System.err.println("bound fail"); future.cause().printStackTrace(); } } }); } }
9.4 從Channel引導客戶端
有時候須要從另外一個Channel引導客戶端,例如寫一個代理或須要從其餘系統檢索數據。從其餘系統獲取數據時比較常見的,有不少Netty應用程序必需要和企業現有的系統集成,如Netty程序與內部系統進行身份驗證,查詢數據庫等。
若是須要在已接受的通道和客戶端通道之間交換數據則須要切換上下文線程。Netty對這方面進行了優化,能夠將已接受的通道經過eventLoop(...)傳遞到EventLoop,從而使客戶端通道在相同的EventLoop裏運行。這消除了額外的上下文切換工做,由於EventLoop繼承於EventLoopGroup。除了消除上下文切換,還能夠在不須要建立多個線程的狀況下使用引導。
爲何要共享EventLoop呢?一個EventLoop由一個線程執行,共享EventLoop能夠肯定全部的Channel都分配給同一線程的EventLoop,這樣就避免了不一樣線程之間切換上下文,從而減小資源開銷。
下圖顯示相同的EventLoop管理兩個Channel:
看下面代碼:
package netty.in.action; import java.net.InetSocketAddress; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; /** * 從Channel引導客戶端 */ public class BootstrapingFromChannel { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .childHandler(new SimpleChannelInboundHandler<ByteBuf>() { ChannelFuture connectFuture; @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { Bootstrap b = new Bootstrap(); b.channel(NioSocketChannel.class).handler( new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("Received data"); msg.clear(); } }); b.group(ctx.channel().eventLoop()); connectFuture = b.connect(new InetSocketAddress("127.0.0.1", 2048)); } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { if (connectFuture.isDone()) { // do something with the data } } }); ChannelFuture f = b.bind(2048); f.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { System.out.println("Server bound"); } else { System.err.println("bound fail"); future.cause().printStackTrace(); } } }); } }
9.5 添加多個ChannelHandler
在全部的例子代碼中,咱們在引導過程當中經過handler(...)或childHandler(...)都只添加了一個ChannelHandler實例,對於簡單的程序可能足夠,可是對於複雜的程序則沒法知足需求。例如,某個程序必須支持多個協議,如HTTP、WebSocket。
若在一個ChannelHandler中處理這些協議將致使一個龐大而複雜的ChannelHandler。Netty經過添加多個ChannelHandler,從而使每一個ChannelHandler分工明確,結構清晰。
Netty的一個優點是能夠在ChannelPipeline中堆疊不少ChannelHandler而且能夠最大程度的重用代碼。
如何添加多個ChannelHandler呢?Netty提供ChannelInitializer抽象類用來初始化ChannelPipeline中的ChannelHandler。ChannelInitializer是一個特殊的ChannelHandler,通道被註冊到EventLoop後就會調用ChannelInitializer,並容許將ChannelHandler添加到CHannelPipeline;完成初始化通道後,這個特殊的ChannelHandler初始化器會從ChannelPipeline中自動刪除。
聽起來很複雜,其實很簡單,看下面代碼:
package netty.in.action; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpObjectAggregator; /** * 使用ChannelInitializer初始化ChannelHandler */ public class InitChannelExample { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializerImpl()); ChannelFuture f = b.bind(2048).sync(); f.channel().closeFuture().sync(); } static final class ChannelInitializerImpl extends ChannelInitializer<Channel>{ @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(new HttpClientCodec()) .addLast(new HttpObjectAggregator(Integer.MAX_VALUE)); } } }
9.6 使用通道選項和屬性
比較麻煩的是建立通道後不得不手動配置每一個通道,爲了不這種狀況,Netty提供了ChannelOption來幫助引導配置。這些選項會自動應用到引導建立的全部通道,可用的各類選項能夠配置底層鏈接的詳細信息,如通道「keep-alive(保持活躍)」或「timeout(超時)」的特性。
屬性能夠將數據和通道以一個安全的方式關聯,這些屬性只是做用於客戶端和服務器的通道。例如,例如客戶端請求web服務器應用程序,爲了跟蹤通道屬於哪一個用戶,應用程序能夠將存儲用的ID做爲通道的一個屬性。任何對象或數據均可以使用屬性被關聯到一個通道。
使用ChannelOption和屬性可讓事情變得很簡單,例如Netty WebSocket服務器根據用戶自動路由消息,經過使用屬性,應用程序能在通道存儲用戶ID以肯定消息應該發送到哪裏。應用程序能夠經過使用一個通道選項進一步自動化,給定時間內沒有收到消息將自動斷開鏈接。看下面代碼:
public static void main(String[] args) { //建立屬性鍵對象 final AttributeKey<Integer> id = AttributeKey.valueOf("ID"); //客戶端引導對象 Bootstrap b = new Bootstrap(); //設置EventLoop,設置通道類型 b.group(new NioEventLoopGroup()).channel(NioSocketChannel.class) //設置ChannelHandler .handler(new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("Reveived data"); msg.clear(); } @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { //通道註冊後執行,獲取屬性值 Integer idValue = ctx.channel().attr(id).get(); System.out.println(idValue); //do something with the idValue } }); //設置通道選項,在通道註冊後或被建立後設置 b.option(ChannelOption.SO_KEEPALIVE, true).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); //設置通道屬性 b.attr(id, 123456); ChannelFuture f = b.connect("www.manning.com",80); f.syncUninterruptibly(); }
前面都是引導基於TCP的SocketChannel,引導也能夠用於無鏈接的傳輸協議如UDP,Netty提供了DatagramChannel,惟一的區別是不會connecte(...),只能bind(...)。看下面代碼:
public static void main(String[] args) { Bootstrap b = new Bootstrap(); b.group(new OioEventLoopGroup()).channel(OioDatagramChannel.class) .handler(new SimpleChannelInboundHandler<DatagramPacket>() { @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { // do something with the packet } }); ChannelFuture f = b.bind(new InetSocketAddress(0)); f.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { System.out.println("Channel bound"); } else { System.err.println("Bound attempt failed"); future.cause().printStackTrace(); } } }); }
Netty有默認的配置設置,多數狀況下,咱們不須要改變這些配置,可是在須要時,咱們能夠細粒度的控制如何工做及處理數據。