上一篇講了AbstractBootstrap,爲這篇作了個鋪墊。java
Bootstrap 是 Netty 提供的一個便利的工廠類, 咱們能夠經過它來完成 Netty 的客戶端或服務器端的 Netty 初始化.
Bootstrap
: 用於客戶端,只須要一個單獨的Channel,來與服務端進行數據交互,對應server端的子Channel。
做用職責
:EventLoop初始化,channel的註冊過程 ,關於pipeline的初始化,handler的添加過程,客戶端鏈接分析。ios
Netty客戶端源碼部分bootstrap
EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) // 註冊線程池 .channel(NioSocketChannel.class) // 使用NioSocketChannel來做爲鏈接用的channel類 .handler(new ChannelInitializer<SocketChannel>() { // 綁定鏈接初始化器 @Override protected void initChannel(SocketChannel ch) throws Exception { //這裏放入自定義助手類 ch.pipeline().addLast(new EchoClientHandler()); } }); ChannelFuture cf = b.connect(host, port).sync(); // 異步鏈接服務器 cf.channel().closeFuture().sync(); // 異步等待關閉鏈接channel } finally { group.shutdownGracefully().sync(); // 釋放線程池資源 } }
從上面的客戶端代碼雖然簡單, 可是卻展現了 Netty 客戶端初始化時所需的全部內容:segmentfault
1. EventLoopGroup: 不管是服務器端仍是客戶端, 都必須指定 EventLoopGroup. 在這個例子中, 指定了 NioEventLoopGroup, 表示一個 NIO 的EventLoopGroup. 2. ChannelType: 指定 Channel 的類型. 由於是客戶端, 所以使用了 NioSocketChannel. 3. Handler: 設置數據的處理器. 4. 這裏的option,提供了一系列的TCP參數
下面咱們深刻代碼, 看一下客戶端經過 Bootstrap 啓動後, 都作了哪些工做.promise
/** * 直接調用父類AbstractBootstrap的方法 */ public B group(EventLoopGroup group) { if (group == null) { throw new NullPointerException("group"); } if (this.group != null) { throw new IllegalStateException("group set already"); } this.group = group; return self(); }
直接調用父類的方法 ,說明該EventLoopGroup,做爲客戶端 Connector 線程,負責註冊監聽鏈接操做位,用於判斷異步鏈接結果。服務器
在 Netty 中, Channel是一個Socket的抽象, 它爲用戶提供了關於 Socket 狀態(是不是鏈接仍是斷開) 以及對 Socket 的讀寫等操做. 每當 Netty 創建了一個鏈接後, 都會有一個對應的 Channel 實例。網絡
/** * 一樣也是直接調用父類AbstractBootstrap的方法 */ public B channel(Class<? extends C> channelClass) { if (channelClass == null) { throw new NullPointerException("channelClass"); } return channelFactory(new ReflectiveChannelFactory<C>(channelClass)); }
咱們再來看下ReflectiveChannelFactory類異步
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> { private final Class<? extends T> clazz; /** * 經過構造函數 傳入 clazz */ public ReflectiveChannelFactory(Class<? extends T> clazz) { if (clazz == null) { throw new NullPointerException("clazz"); } this.clazz = clazz; } /** * 只用這一個方法 經過傳入不一樣的Channel.class 建立不一樣的Channel 對象。 * newChannel() 何時調用呢 仔細追源碼 發現是在綁定 IP 和 端口的 doResolveAndConnect方法裏會調用 */ @Override public T newChannel() { try { return clazz.getConstructor().newInstance(); } catch (Throwable t) { throw new ChannelException("Unable to create Channel from class " + clazz, t); } }
在看channelFactory(new ReflectiveChannelFactory
/** * 建立好Channel後,返回對象Bootstrap自己 */ @Deprecated public B channelFactory(ChannelFactory<? extends C> channelFactory) { if (channelFactory == null) { throw new NullPointerException("channelFactory"); } if (this.channelFactory != null) { throw new IllegalStateException("channelFactory set already"); } this.channelFactory = channelFactory; return self(); }
所以對於咱們這個例子中的客戶端的 Bootstrap 而言, 生成的的 Channel 實例就是 NioSocketChannel。ide
除了 TCP 協議之外, Netty 還支持不少其餘的鏈接協議, 而且每種協議還有 NIO(異步 IO) 和 OIO(Old-IO, 即傳統的阻塞 IO) 版本的區別. 不一樣協議不一樣的阻塞類型的鏈接都有不一樣的 Channel 類型與之對應下面是一些經常使用的 Channel 類型:
- NioSocketChannel, 表明異步的客戶端 TCP Socket 鏈接. - NioServerSocketChannel, 異步的服務器端 TCP Socket 鏈接. - NioDatagramChannel, 異步的 UDP 鏈接 - NioSctpChannel, 異步的客戶端 Sctp 鏈接. - NioSctpServerChannel, 異步的 Sctp 服務器端鏈接. - OioSocketChannel, 同步的客戶端 TCP Socket 鏈接. - OioServerSocketChannel, 同步的服務器端 TCP Socket 鏈接. - OioDatagramChannel, 同步的 UDP 鏈接 - OioSctpChannel, 同步的 Sctp 服務器端鏈接. - OioSctpServerChannel, 同步的客戶端 TCP Socket 鏈接.
Netty 的一個強大和靈活之處就是基於 Pipeline 的自定義 handler 機制
. 基於此, 咱們能夠像添加插件同樣自由組合各類各樣的 handler 來完成業務邏輯. 例如咱們須要處理 HTTP 數據, 那麼就能夠在 pipeline 前添加一個 Http 的編解碼的 Handler, 而後接着添加咱們本身的業務邏輯的 handler, 這樣網絡上的數據流就向經過一個管道同樣, 從不一樣的 handler 中流過並進行編解碼, 最終在到達咱們自定義的 handler 中。
/** * 一樣也是 直接調用父類 AbstractBootstrap 的方法 */ public B handler(ChannelHandler handler) { if (handler == null) { throw new NullPointerException("handler"); } this.handler = handler; return self(); }
不過咱們看到代碼 通常都是這樣寫的
.handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new EchoClientHandler()); } })
那是由於Bootstrap.handler 方法接收一個 ChannelHandler, 而咱們傳遞的是一個 派生於 ChannelInitializer 的匿名類, 它正好也實現了 ChannelHandler 接口. 咱們來看一下, ChannelInitializer 類部分代碼:
/** * ChannelInboundHandlerAdapter 父類的父類 最終會繼承 ChannelHandler * 那麼ChannelInitializer 也就是 ChannelHandler的 子類 */ public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter { private static final InternalLogger logger =InternalLoggerFactory.getInstance(ChannelInitializer.class); /** * 這裏只有這一個抽象類 因此咱們只需重寫這一個方法就能夠了 */ protected abstract void initChannel(C ch) throws Exception; @Override @SuppressWarnings("unchecked") public final void channelRegistered(ChannelHandlerContext ctx) throws Exception { initChannel((C) ctx.channel()); ctx.pipeline().remove(this); ctx.fireChannelRegistered(); } }
ChannelInitializer 是一個抽象類, 它有一個抽象的方法 initChannel
, 咱們正是實現了這個方法, 並添加的自定義的 handler 的. 那麼 initChannel 是哪裏被調用的呢?
答案是 ChannelInitializer.channelRegistered 方法中。
咱們來關注一下 channelRegistered 方法. 從上面的源碼中, 咱們能夠看到, 在 channelRegistered 方法中, 會調用 initChannel 方法, 將自定義的 handler 添加到 ChannelPipeline 中, 而後調用 ctx.pipeline().remove(this) 將本身從 ChannelPipeline 中刪除. 上面的分析過程, 能夠用以下圖片展現:
一開始, ChannelPipeline 中只有三個 handler, head, tail 和咱們添加的 ChannelInitializer.
接着 initChannel 方法調用後, 添加了自定義的 handler
最後將 ChannelInitializer 刪除
/** * 咱們在initChannel抽象方法的實現方法中 經過 SocketChannel得到 ChannelPipeline對象 */ ChannelPipeline p = ch.pipeline(); p.addLast(newEchoClientHandler());
在實例化一個 Channel 時, 會伴隨着一個 ChannelPipeline 的實例化
, 而且此 Channel 會與這個 ChannelPipeline 相互關聯, 這一點能夠經過NioSocketChannel 的父類 AbstractChannel 的構造器:
protected AbstractChannel(Channel parent) { this.parent = parent; unsafe = newUnsafe(); //這個能夠看出 pipeline = new DefaultChannelPipeline(this); }
當實例化一個 Channel(這裏以 EchoClient 爲例, 那麼 Channel 就是 NioSocketChannel), 其 pipeline 字段就是咱們新建立的 DefaultChannelPipeline 對象, 那麼咱們就來看一下 DefaultChannelPipeline 的構造方法。
public DefaultChannelPipeline(AbstractChannel channel) { if (channel == null) { throw new NullPointerException("channel"); } this.channel = channel; tail = new TailContext(this); head = new HeadContext(this); head.next = tail; tail.prev = head; }
咱們調用 DefaultChannelPipeline 的構造器, 傳入了一個 channel, 而這個 channel 其實就是咱們實例化的 NioSocketChannel, DefaultChannelPipeline 會將這個 NioSocketChannel 對象保存在channel 字段中。DefaultChannelPipeline 中, 還有兩個特殊的字段, 即 head
和 tail
, 而這兩個字段是一個雙向鏈表的頭和尾
. 其實在 DefaultChannelPipeline 中, 維護了一個以 AbstractChannelHandlerContext 爲節點的雙向鏈表, 這個鏈表是 Netty 實現 Pipeline 機制的關鍵。
通過上面的各類分析後, 咱們大體瞭解了 Netty 初始化時, 所作的工做, 接下來 分析一下客戶端是如何發起 TCP 鏈接的。
/** * 一、 這裏 終因而Bootstrap 本身的方法了。 傳入IP 地址 和 端口號 */ public ChannelFuture connect(String inetHost, int inetPort) { //經過InetSocketAddress 構造函數 綁定 IP地址+端口號 return connect(InetSocketAddress.createUnresolved(inetHost, inetPort)); } /** * 二、上面調用該方法 ,該方法在調用 doResolveAndConnect方法 */ public ChannelFuture connect(SocketAddress remoteAddress) { if (remoteAddress == null) { throw new NullPointerException("remoteAddress"); } validate(); return doResolveAndConnect(remoteAddress, config.localAddress()); } /** * 三、這步 實例化 Channer */ private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) { //注意 這裏 initAndRegister()方法就是實例化 Channer 的方法 上面說過 真正獲取Channer 對象 是在這步獲取的 final ChannelFuture regFuture = initAndRegister(); final Channel channel = regFuture.channel(); // 這裏省略的 很大一部分邏輯判斷的代碼 return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise()); } /** * 3.1 這裏 就開始 調 newChannel() 方法 也就建立了 Channel 對象 */ final ChannelFuture initAndRegister() { Channel channel = null; channel = channelFactory.newChannel(); return regFuture; } /** * 四、在看doResolveAndConnect0方法 * 這一步仍是對一些 參數數據 進行校驗 省略了校驗代碼 */ private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { // 獲取 當前 EventLoop線程 final EventLoop eventLoop = channel.eventLoop(); final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop); final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress); //這一步 纔是 鏈接的關鍵 doConnect(resolveFuture.getNow(), localAddress, promise); return promise; }
接下來看重要的方法,在 connect 中, 會進行一些參數檢查後, 最終調用的是 doConnect 方法,有關doConnect以後接下來源碼,等本身對Netty瞭解更細緻以後 ,再來寫吧。
這裏推薦一個博主,有關Netty源碼分析的蠻好的:源碼之下無祕密 ── 作最好的 Netty 源碼分析教程
——在本身心情低落的時候,告誡本身不要把負能量帶給別人。(大校13)