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