摘要:對于,目前大家只知道是個線程組,其內部到底如何實現的,它的作用到底是什么,大家也都不太清楚,由于篇幅原因,這里不作詳細介紹,后面會有文章作專門詳解。
在上一篇《ServerBootstrap 與 Bootstrap 初探》中,我們已經初步的了解了ServerBootstrap是netty進行服務端開發的引導類。 且在上一篇的服務端示例中,我們也看到了,在使用netty進行網絡編程時,我們是通過bind方法的調用來完成服務器端口的偵聽:
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() { @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的人可能會有下面的一些疑問:
netty是繼續Java NIO的,那么ServerSocketChannel是什么時候初始化的?
我怎么沒有看到Java NIO中的selector, netty是如何實現多路復用的?
我們設置的handler 或者 childHandler,是如何應用的?
boss線程組 以及 worker線程組 其實如何分配線程的?
為什么是boss線程組,難道接收用戶請求是多個線程一起工作?
。。。
本篇將帶著上面這些疑問,我們將進入bind方法內部,深入淺出,對netty的工作機制一探究竟。
當我們調用ServerBootstrap的bind方法時,其實是調用的是父類AbstractBootstrap的bind方法:
public ChannelFuture bind(int inetPort) { return bind(new InetSocketAddress(inetPort)); }
進而調用另一個重載bind方法:
public ChannelFuture bind(SocketAddress localAddress) { validate(); if (localAddress == null) { throw new NullPointerException("localAddress"); } return doBind(localAddress); }
此bind(SocketAddress localAddress)內部有兩個調用:
1、 調用validate()
顧名思義,validate應該是做校驗,由于ServerBootstrap覆蓋了AbstractBootstrap方法,因此此validate其實是調用ServerBootstrap中的validate方法:
@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沒有意義的;
2、 調用doBind(localAddress)
首先我們先看一下AbstractBootstrap中doBind方法代碼片段:
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方法內部,只做了兩件事:
一、調用了initAndRegister方法
二、調用用了doBind0方法
到底這兩個方法做了啥工作,我們繼續往下分析。
一、首先看看 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, Object> options = options0(); synchronized (options) { setChannelOptions(channel, options, logger); } // 設置引導類配置的attr final Map , Object> attrs = attrs0(); synchronized (attrs) { for (Entry , Object> e: attrs.entrySet()) { @SuppressWarnings("unchecked") AttributeKey
init內部主要做了一下幾件事:
ⅰ、 設置channel的options
final Map, Object> options = options0(); synchronized (options) { setChannelOptions(channel, options, logger); }
ⅱ、設置channel的attribute
final Map, Object> attrs = attrs0(); synchronized (attrs) { for (Entry , Object> e: attrs.entrySet()) { @SuppressWarnings("unchecked") AttributeKey key = (AttributeKey ) 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 config();
所以具體的實現細節還是在子類ServerBootstrap中:
@Override public final ServerBootstrapConfig config() { return config; }
此方法只會返回了config實例對象,此屬性是在ServerBootstrap初始化時就創建了
public class ServerBootstrap extends AbstractBootstrap{ ... 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,目前大家只知道是個線程組,其內部到底如何實現的,它的作用到底是什么,大家也都不太清楚,由于篇幅原因,這里不作詳細介紹,后面會有文章作專門詳解。
二、我們再回到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工作執行完成。
此篇對服務端綁定的流程做了大體介紹,但由于篇幅問題,下面幾個問題未做詳盡分析:
1、 NioServerSocketChannel是如何實例化的
2、 Pipeline是什么,為什么要通過它添加handler
3、 NioEventLoopGroup內部細節是什么,為什么要通過它注冊Channel, Java NIO中channel初始化后不是要注冊到selector中嗎?
帶著上面這些疑問,歡迎大家繼續關注接下來的幾篇文章,在這幾篇文章中,bind操作會一直貫穿其中:
Netty4.x 源碼實戰系列(三):NioServerSocketChannel全剖析
Netty4.x 源碼實戰系列(四):Pipeline全剖析
Netty4.x 源碼實戰系列(五):NioEventLoopGroup全剖析
Netty4.x 源碼實戰系列(六):NioEventLoop全剖析
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68105.html
摘要:本篇將通過實例化過程,來深入剖析。及初始化完成后,它們會相互連接。我們在回到的構造方法父類構造方法調用完成后,還要初始化一下自己的配置對象是的內部類而又是繼承自,通過代碼分析,此對象就是就會對底層一些配置設置行為的封裝。 根據上一篇《Netty4.x 源碼實戰系列(二):服務端bind流程詳解》所述,在進行服務端開發時,必須通過ServerBootstrap引導類的channel方法來...
摘要:在上一篇源碼實戰系列三全剖析中,我們詳細分析了的初始化過程,并得出了如下結論在中,每一個都有一個對象,并且其內部本質上就是一個雙向鏈表本篇我們將深入源碼內部,對其一探究竟,給大家一個全方位解析。 在上一篇《Netty4.x 源碼實戰系列(三):NioServerSocketChannel全剖析》中,我們詳細分析了NioServerSocketChannel的初始化過程,并得出了如下結論...
摘要:而用于主線程池的屬性都定義在中本篇只是簡單介紹了一下引導類的配置屬性,下一篇我將詳細介紹服務端引導類的過程分析。 從Java1.4開始, Java引入了non-blocking IO,簡稱NIO。NIO與傳統socket最大的不同就是引入了Channel和多路復用selector的概念。傳統的socket是基于stream的,它是單向的,有InputStream表示read和Outpu...
摘要:我想這很好的解釋了中,僅僅一個都這么復雜,在單線程或者說串行的程序中,編程往往是很簡單的,說白了就是調用,調用,調用然后返回。 Netty源碼分析(三) 前提概要 這次停更很久了,原因是中途迷茫了一段時間,不過最近調整過來了。不過有點要說下,前幾天和業內某個大佬聊天,收獲很多,所以這篇博文和之前也會不太一樣,我們會先從如果是我自己去實現這個功能需要怎么做開始,然后去看netty源碼,與...
摘要:接下來的兩篇文章,我將從源碼角度為大家深入淺出的剖析的線程模型工作機制。我們看一下的源碼通過的代碼發現,實現了接口,其內部會通過指定的默認線程工廠來創建線程,并執行相應的任務。至此,初始化完成了。下一篇我們將詳細介紹,敬請期待。 我們都知道Netty的線程模型是基于React的線程模型,并且我們都知道Netty是一個高性能的NIO框架,那么其線程模型必定是它的重要貢獻之一。 在使用ne...
閱讀 2404·2021-11-24 09:39
閱讀 3223·2021-10-09 09:53
閱讀 1130·2021-09-22 16:06
閱讀 4442·2021-09-02 10:18
閱讀 800·2021-08-23 09:42
閱讀 1761·2021-08-17 10:11
閱讀 2685·2019-08-30 13:02
閱讀 2121·2019-08-30 12:49