在上一篇《ServerBootstrap 與 Bootstrap 初探》中,咱們已經初步的瞭解了ServerBootstrap是netty進行服務端開發的引導類。 且在上一篇的服務端示例中,咱們也看到了,在使用netty進行網絡編程時,咱們是經過bind方法的調用來完成服務器端口的偵聽:java
EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler()) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new DiscardServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); // 偵聽8000端口 ChannelFuture f = b.bind(8000).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); }
從上面的服務端示例中能夠看到,咱們只是定義了主線程組及worker線程組,以及指定了channel類型爲NioServerSocketChannel等等一些簡單的配置, 而後綁定偵聽端口,用於網絡服務的主體代碼基本就完了(業務代碼在Handler中實現,後面的文章會詳細介紹。
這真的大大簡化並方便了java程序員使用netty來進行網絡開發,可是想要深刻學習netty的人可能會有下面的一些疑問:程序員
本篇將帶着上面這些疑問,咱們將進入bind方法內部,深刻淺出,對netty的工做機制一探究竟。編程
當咱們調用ServerBootstrap的bind方法時,實際上是調用的是父類AbstractBootstrap的bind方法:bootstrap
public ChannelFuture bind(int inetPort) { return bind(new InetSocketAddress(inetPort)); }
進而調用另外一個重載bind方法:segmentfault
public ChannelFuture bind(SocketAddress localAddress) { validate(); if (localAddress == null) { throw new NullPointerException("localAddress"); } return doBind(localAddress); }
此bind(SocketAddress localAddress)內部有兩個調用:
一、 調用validate()
顧名思義,validate應該是作校驗,因爲ServerBootstrap覆蓋了AbstractBootstrap方法,所以此validate實際上是調用ServerBootstrap中的validate方法:promise
@Override public ServerBootstrap validate() { super.validate(); if (childHandler == null) { throw new IllegalStateException("childHandler not set"); } if (childGroup == null) { logger.warn("childGroup is not set. Using parentGroup instead."); childGroup = config.group(); } return this; }
在子類ServerBootstrap的validate方法中,首先它有調用了基類的validate()方法:服務器
public B validate() { if (group == null) { throw new IllegalStateException("group not set"); } if (channelFactory == null) { throw new IllegalStateException("channel or channelFactory not set"); } return self(); }
因此經過validate方法咱們得出以下結論:網絡
1) 服務端程序必需要設置boss線程組 以及 worker線程組,分別用於Acceptor 以及 I/O操做;
2)必須經過Boostrap的channel()來指定通道類型,用來生成相應的通道(ServerSocketChannel或SocketChannel);
3) 由於是服務端程序,因此必須設置ChildHandler,來指定業務處理器,不然沒有業務處理的服務器hi沒有意義的;
二、 調用doBind(localAddress)
首先咱們先看一下AbstractBootstrap中doBind方法代碼片斷:ide
private ChannelFuture doBind(final SocketAddress localAddress) { final ChannelFuture regFuture = initAndRegister(); // (1) final Channel channel = regFuture.channel(); // (2) if (regFuture.cause() != null) { return regFuture; } if (regFuture.isDone()) { ChannelPromise promise = channel.newPromise(); doBind0(regFuture, channel, localAddress, promise); // (3) return promise; } else { final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel); regFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { Throwable cause = future.cause(); if (cause != null) { promise.setFailure(cause); } else { promise.registered(); doBind0(regFuture, channel, localAddress, promise); // (3) } } }); return promise; } }
剝去無用代碼,其實doBind方法內部,只作了兩件事:
1、調用了initAndRegister方法
2、調用用了doBind0方法
到底這兩個方法作了啥工做,咱們繼續往下分析。oop
1、首先看看 initAndRegister方法內部代碼:
final ChannelFuture initAndRegister() { Channel channel = null; try { // (1) 調用工廠方法,生成channel實例 channel = channelFactory.newChannel(); // (2) 初始化通道信息 init(channel); } catch (Throwable t) { if (channel != null) { channel.unsafe().closeForcibly(); } return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t); } // (3) 註冊通道 ChannelFuture regFuture = config().group().register(channel); if (regFuture.cause() != null) { if (channel.isRegistered()) { channel.close(); } else { channel.unsafe().closeForcibly(); } } return regFuture; }
經過上面代碼,咱們能夠看出initAndRegister方法作了三件事:
①、調用channelFactory生成通道channel實例:
在上一篇中,咱們已經知道,經過serverbootstrap的channel方法來指定通道類型,實際上是調用基類AbstractBoostrap的channel方法,其內部實際上是實例化了一個產生指定channel類型的channelFactory。
因此,initAndRegister中的channelFactory.newChannel()方法就是生成了一個NioServerSocketChannel的實例。 關於NioServerSocketChannel內部細節,我會有專門的文章進行分析,此處不作詳述。
②、調用init(channel)初始化通道信息
init方法在基類AbstractBootstrap中是一個抽象方法:
abstract void init(Channel channel) throws Exception;
因此此處init的具體實如今子類ServerBootstrap類中:
@Override void init(Channel channel) throws Exception { // 設置引導類配置的option final Map<ChannelOption<?>, Object> options = options0(); synchronized (options) { setChannelOptions(channel, options, logger); } // 設置引導類配置的attr final Map<AttributeKey<?>, Object> attrs = attrs0(); synchronized (attrs) { for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) { @SuppressWarnings("unchecked") AttributeKey<Object> key = (AttributeKey<Object>) e.getKey(); channel.attr(key).set(e.getValue()); } } // 獲取當前通道的pipeline ChannelPipeline p = channel.pipeline(); final EventLoopGroup currentChildGroup = childGroup; final ChannelHandler currentChildHandler = childHandler; final Entry<ChannelOption<?>, Object>[] currentChildOptions; final Entry<AttributeKey<?>, Object>[] currentChildAttrs; synchronized (childOptions) { currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size())); } synchronized (childAttrs) { currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size())); } // 給NioServerSocketChannel的pipeline中添加一個ChannelInitializer類型的Handler p.addLast(new ChannelInitializer<Channel>() { @Override public void initChannel(final Channel ch) throws Exception { final ChannelPipeline pipeline = ch.pipeline(); ChannelHandler handler = config.handler(); if (handler != null) { pipeline.addLast(handler); } ch.eventLoop().execute(new Runnable() { @Override public void run() { pipeline.addLast(new ServerBootstrapAcceptor( ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); } }); } }); }
init內部主要作了一下幾件事:
ⅰ、 設置channel的options
final Map<ChannelOption<?>, Object> options = options0(); synchronized (options) { setChannelOptions(channel, options, logger); }
ⅱ、設置channel的attribute
final Map<AttributeKey<?>, Object> attrs = attrs0(); synchronized (attrs) { for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) { @SuppressWarnings("unchecked") AttributeKey<Object> key = (AttributeKey<Object>) e.getKey(); channel.attr(key).set(e.getValue()); } }
ⅲ、給NioServerSocketChannel的pipeline中添加一個ChannelInitializer類型的Handler(根據類繼承ChannelInitializer繼承自ChannelInboundHandlerAdapter)
關於pipeline究竟是什麼,本篇不作詳述,下一篇我會跟NioServerSocketChannel來一塊兒給你們分析一下。
③、完成通道的註冊
通道初始化完成後,而後就能夠註冊通道了:
ChannelFuture regFuture = config().group().register(channel);
config()在AbstractBootstrap中也是個抽象方法:
public abstract AbstractBootstrapConfig<B, C> config();
因此具體的實現細節仍是在子類ServerBootstrap中:
@Override public final ServerBootstrapConfig config() { return config; }
此方法只會返回了config實例對象,此屬性是在ServerBootstrap初始化時就建立了
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> { ... private final ServerBootstrapConfig config = new ServerBootstrapConfig(this); ... @Override public final ServerBootstrapConfig config() { return config; } }
咱們先看一下ServerBootstrapConfig的類繼承結構圖:
ServerBootstrapConfig初始化時傳入的this對象,此this表示ServerBootstrap,並且ServerBootstrapConfig構造方法內部調用了其基類AbstractBootstrapConfig的構造方法:
ServerBootstrapConfig(ServerBootstrap bootstrap) { super(bootstrap); }
因此config().group()就對應ServerBootstrap的group屬性引用(由上一篇得知group指向boss線程組),所以register實際上是調用的NioEventLoopGroup的register方法。
對於NioEventLoopGroup,目前你們只知道是個線程組,其內部到底如何實現的,它的做用究竟是什麼,你們也都不太清楚,因爲篇幅緣由,這裏不做詳細介紹,後面會有文章做專門詳解。
2、咱們再回到doBind(localAddress)方法,內部在調用玩initAndRegister以後,就是調用doBind0方法
private static void doBind0( final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) { // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up // the pipeline in its channelRegistered() implementation. channel.eventLoop().execute(new Runnable() { @Override public void run() { if (regFuture.isSuccess()) { channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } else { promise.setFailure(regFuture.cause()); } } }); }
此方法內部就是完成channel的偵聽端口的綁定。
至此ServerBootstrap的bind工做執行完成。
此篇對服務端綁定的流程作了大致介紹,但因爲篇幅問題,下面幾個問題未作詳盡分析:
一、 NioServerSocketChannel是如何實例化的
二、 Pipeline是什麼,爲何要經過它添加handler
三、 NioEventLoopGroup內部細節是什麼,爲何要經過它註冊Channel, Java NIO中channel初始化後不是要註冊到selector中嗎?
帶着上面這些疑問,歡迎你們繼續關注接下來的幾篇文章,在這幾篇文章中,bind操做會一直貫穿其中:
Netty4.x 源碼實戰系列(三):NioServerSocketChannel全剖析
Netty4.x 源碼實戰系列(四):Pipeline全剖析Netty4.x 源碼實戰系列(五):NioEventLoopGroup全剖析Netty4.x 源碼實戰系列(六):NioEventLoop全剖析