摘要:目錄源碼分析之番外篇的前生今世的前生今世之一簡(jiǎn)介的前生今世之二小結(jié)的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環(huán)境搭建源碼分析之一揭開(kāi)神秘的紅蓋頭源碼分析之一揭開(kāi)神秘的紅蓋頭客戶端源碼分析之一揭開(kāi)神秘的紅蓋頭服務(wù)器
目錄
Netty 源碼分析之 番外篇 Java NIO 的前生今世
Java NIO 的前生今世 之一 簡(jiǎn)介
Java NIO 的前生今世 之二 NIO Channel 小結(jié)
Java NIO 的前生今世 之三 NIO Buffer 詳解
Java NIO 的前生今世 之四 NIO Selector 詳解
Netty 源碼分析之 零 磨刀不誤砍柴工 源碼分析環(huán)境搭建
Netty 源碼分析之 一 揭開(kāi) Bootstrap 神秘的紅蓋頭
Netty 源碼分析之 一 揭開(kāi) Bootstrap 神秘的紅蓋頭 (客戶端)
Netty 源碼分析之 一 揭開(kāi) Bootstrap 神秘的紅蓋頭 (服務(wù)器端)
服務(wù)器端在分析客戶端的代碼時(shí), 我們已經(jīng)對(duì) Bootstrap 啟動(dòng) Netty 有了一個(gè)大致的認(rèn)識(shí), 那么接下來(lái)分析服務(wù)器端時(shí), 就會(huì)相對(duì)簡(jiǎn)單一些了.
首先還是來(lái)看一下服務(wù)器端的啟動(dòng)代碼:
public final class EchoServer { static final boolean SSL = System.getProperty("ssl") != null; static final int PORT = Integer.parseInt(System.getProperty("port", "8007")); public static void main(String[] args) throws Exception { // Configure SSL. final SslContext sslCtx; if (SSL) { SelfSignedCertificate ssc = new SelfSignedCertificate(); sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); } else { sslCtx = null; } // Configure the server. EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast(sslCtx.newHandler(ch.alloc())); } //p.addLast(new LoggingHandler(LogLevel.INFO)); p.addLast(new EchoServerHandler()); } }); // Start the server. ChannelFuture f = b.bind(PORT).sync(); // Wait until the server socket is closed. f.channel().closeFuture().sync(); } finally { // Shut down all event loops to terminate all threads. bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
和客戶端的代碼相比, 沒(méi)有很大的差別, 基本上也是進(jìn)行了如下幾個(gè)部分的初始化:
EventLoopGroup: 不論是服務(wù)器端還是客戶端, 都必須指定 EventLoopGroup. 在這個(gè)例子中, 指定了 NioEventLoopGroup, 表示一個(gè) NIO 的EventLoopGroup, 不過(guò)服務(wù)器端需要指定兩個(gè) EventLoopGroup, 一個(gè)是 bossGroup, 用于處理客戶端的連接請(qǐng)求; 另一個(gè)是 workerGroup, 用于處理與各個(gè)客戶端連接的 IO 操作.
ChannelType: 指定 Channel 的類(lèi)型. 因?yàn)槭欠?wù)器端, 因此使用了 NioServerSocketChannel.
Handler: 設(shè)置數(shù)據(jù)的處理器.
Channel 的初始化過(guò)程我們?cè)诜治隹蛻舳说?Channel 初始化過(guò)程時(shí), 已經(jīng)提到, Channel 是對(duì) Java 底層 Socket 連接的抽象, 并且知道了客戶端的 Channel 的具體類(lèi)型是 NioSocketChannel, 那么自然的, 服務(wù)器端的 Channel 類(lèi)型就是 NioServerSocketChannel 了.
那么接下來(lái)我們按照分析客戶端的流程對(duì)服務(wù)器端的代碼也同樣地分析一遍, 這樣也方便我們對(duì)比一下服務(wù)器端和客戶端有哪些不一樣的地方.
同樣的分析套路, 我們已經(jīng)知道了, 在客戶端中, Channel 的類(lèi)型其實(shí)是在初始化時(shí), 通過(guò) Bootstrap.channel() 方法設(shè)置的, 服務(wù)器端自然也不例外.
在服務(wù)器端, 我們調(diào)用了 ServerBootstarap.channel(NioServerSocketChannel.class), 傳遞了一個(gè) NioServerSocketChannel Class 對(duì)象. 這樣的話, 按照和分析客戶端代碼一樣的流程, 我們就可以確定, NioServerSocketChannel 的實(shí)例化是通過(guò) BootstrapChannelFactory 工廠類(lèi)來(lái)完成的, 而 BootstrapChannelFactory 中的 clazz 字段被設(shè)置為了 NioServerSocketChannel.class, 因此當(dāng)調(diào)用 BootstrapChannelFactory.newChannel() 時(shí):
@Override public T newChannel() { // 刪除 try 塊 return clazz.newInstance(); }
就獲取到了一個(gè) NioServerSocketChannel 的實(shí)例.
最后我們也來(lái)總結(jié)一下:
ServerBootstrap 中的 ChannelFactory 的實(shí)現(xiàn)是 BootstrapChannelFactory
生成的 Channel 的具體類(lèi)型是 NioServerSocketChannel.
Channel 的實(shí)例化過(guò)程, 其實(shí)就是調(diào)用的 ChannelFactory.newChannel 方法, 而實(shí)例化的 Channel 的具體的類(lèi)型又是和在初始化 ServerBootstrap 時(shí)傳入的 channel() 方法的參數(shù)相關(guān). 因此對(duì)于我們這個(gè)例子中的服務(wù)器端的 ServerBootstrap 而言, 生成的的 Channel 實(shí)例就是 NioServerSocketChannel.
首先還是來(lái)看一下 NioServerSocketChannel 的實(shí)例化過(guò)程.
下面是 NioServerSocketChannel 的類(lèi)層次結(jié)構(gòu)圖:
首先, 我們來(lái)看一下它的默認(rèn)的構(gòu)造器. 和 NioSocketChannel 類(lèi)似, 構(gòu)造器都是調(diào)用了 newSocket 來(lái)打開(kāi)一個(gè) Java 的 NIO Socket, 不過(guò)需要注意的是, 客戶端的 newSocket 調(diào)用的是 openSocketChannel, 而服務(wù)器端的 newSocket 調(diào)用的是 openServerSocketChannel. 顧名思義, 一個(gè)是客戶端的 Java SocketChannel, 一個(gè)是服務(wù)器端的 Java ServerSocketChannel.
private static ServerSocketChannel newSocket(SelectorProvider provider) { return provider.openServerSocketChannel(); } public NioServerSocketChannel() { this(newSocket(DEFAULT_SELECTOR_PROVIDER)); }
接下來(lái)會(huì)調(diào)用重載的構(gòu)造器:
public NioServerSocketChannel(ServerSocketChannel channel) { super(null, channel, SelectionKey.OP_ACCEPT); config = new NioServerSocketChannelConfig(this, javaChannel().socket()); }
這個(gè)構(gòu)造其中, 調(diào)用父類(lèi)構(gòu)造器時(shí), 傳入的參數(shù)是 SelectionKey.OP_ACCEPT. 作為對(duì)比, 我們回想一下, 在客戶端的 Channel 初始化時(shí), 傳入的參數(shù)是 SelectionKey.OP_READ. 有 Java NIO Socket 開(kāi)發(fā)經(jīng)驗(yàn)的朋友就知道了, Java NIO 是一種 Reactor 模式, 我們通過(guò) selector 來(lái)實(shí)現(xiàn) I/O 的多路復(fù)用復(fù)用. 在一開(kāi)始時(shí), 服務(wù)器端需要監(jiān)聽(tīng)客戶端的連接請(qǐng)求, 因此在這里我們?cè)O(shè)置了 SelectionKey.OP_ACCEPT, 即通知 selector 我們對(duì)客戶端的連接請(qǐng)求感興趣.
接著和客戶端的分析一下, 會(huì)逐級(jí)地調(diào)用父類(lèi)的構(gòu)造器 NioServerSocketChannel <- AbstractNioMessageChannel <- AbstractNioChannel <- AbstractChannel.
同樣的, 在 AbstractChannel 中會(huì)實(shí)例化一個(gè) unsafe 和 pipeline:
protected AbstractChannel(Channel parent) { this.parent = parent; unsafe = newUnsafe(); pipeline = new DefaultChannelPipeline(this); }
不過(guò), 這里有一點(diǎn)需要注意的是, 客戶端的 unsafe 是一個(gè) AbstractNioByteChannel#NioByteUnsafe 的實(shí)例, 而在服務(wù)器端時(shí), 因?yàn)?AbstractNioMessageChannel 重寫(xiě)了newUnsafe 方法:
@Override protected AbstractNioUnsafe newUnsafe() { return new NioMessageUnsafe(); }
因此在服務(wù)器端, unsafe 字段其實(shí)是一個(gè) AbstractNioMessageChannel#AbstractNioUnsafe 的實(shí)例.
我們來(lái)總結(jié)一下, 在 NioServerSocketChannsl 實(shí)例化過(guò)程中, 所需要做的工作:
調(diào)用 NioServerSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER) 打開(kāi)一個(gè)新的 Java NIO ServerSocketChannel
AbstractChannel(Channel parent) 中初始化 AbstractChannel 的屬性:
parent 屬性置為 null
unsafe 通過(guò)newUnsafe() 實(shí)例化一個(gè) unsafe 對(duì)象, 它的類(lèi)型是 AbstractNioMessageChannel#AbstractNioUnsafe 內(nèi)部類(lèi)
pipeline 是 new DefaultChannelPipeline(this) 新創(chuàng)建的實(shí)例.
AbstractNioChannel 中的屬性:
SelectableChannel ch 被設(shè)置為 Java ServerSocketChannel, 即 NioServerSocketChannel#newSocket 返回的 Java NIO ServerSocketChannel.
readInterestOp 被設(shè)置為 SelectionKey.OP_ACCEPT
SelectableChannel ch 被配置為非阻塞的 ch.configureBlocking(false)
NioServerSocketChannel 中的屬性:
ServerSocketChannelConfig config = new NioServerSocketChannelConfig(this, javaChannel().socket())
ChannelPipeline 初始化服務(wù)器端和客戶端的 ChannelPipeline 的初始化一致, 因此就不再多帶帶分析了.
Channel 的注冊(cè)服務(wù)器端和客戶端的 Channel 的注冊(cè)過(guò)程一致, 因此就不再多帶帶分析了.
關(guān)于 bossGroup 與 workerGroup在客戶端的時(shí)候, 我們只提供了一個(gè) EventLoopGroup 對(duì)象, 而在服務(wù)器端的初始化時(shí), 我們?cè)O(shè)置了兩個(gè) EventLoopGroup, 一個(gè)是 bossGroup, 另一個(gè)是 workerGroup. 那么這兩個(gè) EventLoopGroup 都是干什么用的呢? 其實(shí)呢, bossGroup 是用于服務(wù)端 的 accept 的, 即用于處理客戶端的連接請(qǐng)求. 我們可以把 Netty 比作一個(gè)飯店, bossGroup 就像一個(gè)像一個(gè)前臺(tái)接待, 當(dāng)客戶來(lái)到飯店吃時(shí), 接待員就會(huì)引導(dǎo)顧客就坐, 為顧客端茶送水等. 而 workerGroup, 其實(shí)就是實(shí)際上干活的啦, 它們負(fù)責(zé)客戶端連接通道的 IO 操作: 當(dāng)接待員 招待好顧客后, 就可以稍做休息, 而此時(shí)后廚里的廚師們(workerGroup)就開(kāi)始忙碌地準(zhǔn)備飯菜了.
關(guān)于 bossGroup 與 workerGroup 的關(guān)系, 我們可以用如下圖來(lái)展示:
首先, 服務(wù)器端 bossGroup 不斷地監(jiān)聽(tīng)是否有客戶端的連接, 當(dāng)發(fā)現(xiàn)有一個(gè)新的客戶端連接到來(lái)時(shí), bossGroup 就會(huì)為此連接初始化各項(xiàng)資源, 然后從 workerGroup 中選出一個(gè) EventLoop 綁定到此客戶端連接中. 那么接下來(lái)的服務(wù)器與客戶端的交互過(guò)程就全部在此分配的 EventLoop 中了.
口說(shuō)無(wú)憑, 我們還是以源碼說(shuō)話吧.
首先在ServerBootstrap 初始化時(shí), 調(diào)用了 b.group(bossGroup, workerGroup) 設(shè)置了兩個(gè) EventLoopGroup, 我們跟蹤進(jìn)去看一下:
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) { super.group(parentGroup); ... this.childGroup = childGroup; return this; }
顯然, 這個(gè)方法初始化了兩個(gè)字段, 一個(gè)是 group = parentGroup, 它是在 super.group(parentGroup) 中初始化的, 另一個(gè)是 childGroup = childGroup. 接著我們啟動(dòng)程序調(diào)用了 b.bind 方法來(lái)監(jiān)聽(tīng)一個(gè)本地端口. bind 方法會(huì)觸發(fā)如下的調(diào)用鏈:
AbstractBootstrap.bind -> AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister
AbstractBootstrap.initAndRegister 是我們的老朋友了, 我們?cè)诜治隹蛻舳顺绦驎r(shí), 和它打過(guò)很多交到了, 我們?cè)賮?lái)回顧一下這個(gè)方法吧:
final ChannelFuture initAndRegister() { final Channel channel = channelFactory().newChannel(); ... 省略異常判斷 init(channel); ChannelFuture regFuture = group().register(channel); return regFuture; }
這里 group() 方法返回的是上面我們提到的 bossGroup, 而這里的 channel 我們也已經(jīng)分析過(guò)了, 它是一個(gè)是一個(gè) NioServerSocketChannsl 實(shí)例, 因此我們可以知道, group().register(channel) 將 bossGroup 和 NioServerSocketChannsl 關(guān)聯(lián)起來(lái)了.
那么 workerGroup 是在哪里與 NioSocketChannel 關(guān)聯(lián)的呢?
我們繼續(xù)看 init(channel) 方法:
@Override void init(Channel channel) throws Exception { ... ChannelPipeline p = channel.pipeline(); final EventLoopGroup currentChildGroup = childGroup; final ChannelHandler currentChildHandler = childHandler; final Entry, Object>[] currentChildOptions; final Entry , Object>[] currentChildAttrs; p.addLast(new ChannelInitializer () { @Override public void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); ChannelHandler handler = handler(); if (handler != null) { pipeline.addLast(handler); } pipeline.addLast(new ServerBootstrapAcceptor( currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); } }); }
init 方法在 ServerBootstrap 中重寫(xiě)了, 從上面的代碼片段中我們看到, 它為 pipeline 中添加了一個(gè) ChannelInitializer, 而這個(gè) ChannelInitializer 中添加了一個(gè)關(guān)鍵的 ServerBootstrapAcceptor handler. 關(guān)于 handler 的添加與初始化的過(guò)程, 我們留待下一小節(jié)中分析, 我們現(xiàn)在關(guān)注一下 ServerBootstrapAcceptor 類(lèi).
ServerBootstrapAcceptor 中重寫(xiě)了 channelRead 方法, 其主要代碼如下:
@Override @SuppressWarnings("unchecked") public void channelRead(ChannelHandlerContext ctx, Object msg) { final Channel child = (Channel) msg; child.pipeline().addLast(childHandler); ... childGroup.register(child).addListener(...); }
ServerBootstrapAcceptor 中的 childGroup 是構(gòu)造此對(duì)象是傳入的 currentChildGroup, 即我們的 workerGroup, 而 Channel 是一個(gè) NioSocketChannel 的實(shí)例, 因此這里的 childGroup.register 就是將 workerGroup 中的摸個(gè) EventLoop 和 NioSocketChannel 關(guān)聯(lián)了. 既然這樣, 那么現(xiàn)在的問(wèn)題是, ServerBootstrapAcceptor.channelRead 方法是怎么被調(diào)用的呢? 其實(shí)當(dāng)一個(gè) client 連接到 server 時(shí), Java 底層的 NIO ServerSocketChannel 會(huì)有一個(gè) SelectionKey.OP_ACCEPT 就緒事件, 接著就會(huì)調(diào)用到 NioServerSocketChannel.doReadMessages:
@Override protected int doReadMessages(List
在 doReadMessages 中, 通過(guò) javaChannel().accept() 獲取到客戶端新連接的 SocketChannel, 接著就實(shí)例化一個(gè) NioSocketChannel, 并且傳入 NioServerSocketChannel 對(duì)象(即 this), 由此可知, 我們創(chuàng)建的這個(gè) NioSocketChannel 的父 Channel 就是 NioServerSocketChannel 實(shí)例 .
接下來(lái)就經(jīng)由 Netty 的 ChannelPipeline 機(jī)制, 將讀取事件逐級(jí)發(fā)送到各個(gè) handler 中, 于是就會(huì)觸發(fā)前面我們提到的 ServerBootstrapAcceptor.channelRead 方法啦.
服務(wù)器端的 handler 的添加過(guò)程和客戶端的有點(diǎn)區(qū)別, 和 EventLoopGroup 一樣, 服務(wù)器端的 handler 也有兩個(gè), 一個(gè)是通過(guò) handler() 方法設(shè)置 handler 字段, 另一個(gè)是通過(guò) childHandler() 設(shè)置 childHandler 字段. 通過(guò)前面的 bossGroup 和 workerGroup 的分析, 其實(shí)我們?cè)谶@里可以大膽地猜測(cè): handler 字段與 accept 過(guò)程有關(guān), 即這個(gè) handler 負(fù)責(zé)處理客戶端的連接請(qǐng)求; 而 childHandler 就是負(fù)責(zé)和客戶端的連接的 IO 交互.
那么實(shí)際上是不是這樣的呢? 來(lái), 我們繼續(xù)通過(guò)代碼證明.
在 關(guān)于 bossGroup 與 workerGroup 小節(jié)中, 我們提到, ServerBootstrap 重寫(xiě)了 init 方法, 在這個(gè)方法中添加了 handler:
@Override void init(Channel channel) throws Exception { ... ChannelPipeline p = channel.pipeline(); final EventLoopGroup currentChildGroup = childGroup; final ChannelHandler currentChildHandler = childHandler; final Entry, Object>[] currentChildOptions; final Entry , Object>[] currentChildAttrs; p.addLast(new ChannelInitializer () { @Override public void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); ChannelHandler handler = handler(); if (handler != null) { pipeline.addLast(handler); } pipeline.addLast(new ServerBootstrapAcceptor( currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); } }); }
上面代碼的 initChannel 方法中, 首先通過(guò) handler() 方法獲取一個(gè) handler, 如果獲取的 handler 不為空,則添加到 pipeline 中. 然后接著, 添加了一個(gè) ServerBootstrapAcceptor 實(shí)例. 那么這里 handler() 方法返回的是哪個(gè)對(duì)象呢? 其實(shí)它返回的是 handler 字段, 而這個(gè)字段就是我們?cè)诜?wù)器端的啟動(dòng)代碼中設(shè)置的:
b.group(bossGroup, workerGroup) ... .handler(new LoggingHandler(LogLevel.INFO))
那么這個(gè)時(shí)候, pipeline 中的 handler 情況如下:
根據(jù)我們?cè)瓉?lái)分析客戶端的經(jīng)驗(yàn), 我們指定, 當(dāng) channel 綁定到 eventLoop 后(在這里是 NioServerSocketChannel 綁定到 bossGroup)中時(shí), 會(huì)在 pipeline 中發(fā)出 fireChannelRegistered 事件, 接著就會(huì)觸發(fā) ChannelInitializer.initChannel 方法的調(diào)用.
因此在綁定完成后, 此時(shí)的 pipeline 的內(nèi)如如下:
前面我們?cè)诜治?bossGroup 和 workerGroup 時(shí), 已經(jīng)知道了在 ServerBootstrapAcceptor.channelRead 中會(huì)為新建的 Channel 設(shè)置 handler 并注冊(cè)到一個(gè) eventLoop 中, 即:
@Override @SuppressWarnings("unchecked") public void channelRead(ChannelHandlerContext ctx, Object msg) { final Channel child = (Channel) msg; child.pipeline().addLast(childHandler); ... childGroup.register(child).addListener(...); }
而這里的 childHandler 就是我們?cè)诜?wù)器端啟動(dòng)代碼中設(shè)置的 handler:
b.group(bossGroup, workerGroup) ... .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast(sslCtx.newHandler(ch.alloc())); } //p.addLast(new LoggingHandler(LogLevel.INFO)); p.addLast(new EchoServerHandler()); } });
后續(xù)的步驟就沒(méi)有什么好說(shuō)的了, 當(dāng)這個(gè)客戶端連接 Channel 注冊(cè)后, 就會(huì)觸發(fā) ChannelInitializer.initChannel 方法的調(diào)用, 此后的客戶端連接的 ChannelPipeline 狀態(tài)如下:
最后我們來(lái)總結(jié)一下服務(wù)器端的 handler 與 childHandler 的區(qū)別與聯(lián)系:
在服務(wù)器 NioServerSocketChannel 的 pipeline 中添加的是 handler 與 ServerBootstrapAcceptor.
當(dāng)有新的客戶端連接請(qǐng)求時(shí), ServerBootstrapAcceptor.channelRead 中負(fù)責(zé)新建此連接的 NioSocketChannel 并添加 childHandler 到 NioSocketChannel 對(duì)應(yīng)的 pipeline 中, 并將此 channel 綁定到 workerGroup 中的某個(gè) eventLoop 中.
handler 是在 accept 階段起作用, 它處理客戶端的連接請(qǐng)求.
childHandler 是在客戶端連接建立以后起作用, 它負(fù)責(zé)客戶端連接的 IO 交互.
下面我們用一幅圖來(lái)總結(jié)一下服務(wù)器端的 handler 添加流程:
后記這是 Netty 源碼分析 系列教程的第一篇, 按我的計(jì)劃, 這一篇文章是一個(gè)簡(jiǎn)述性質(zhì)的, 即這里會(huì)涉及到 Netty 各個(gè)功能模塊, 但是我只是簡(jiǎn)單地提了一下, 而沒(méi)有深入地探索它們內(nèi)部的實(shí)現(xiàn)機(jī)理. 之所以這樣做, 第一, 是因?yàn)槿绻簧蟻?lái)就從細(xì)節(jié)分析, 那么未免會(huì)陷入各種瑣碎的細(xì)節(jié)中難以自拔; 第二, 我想給讀者展示一個(gè)一個(gè)完整的 Netty 的運(yùn)行流程, 讓讀者從一個(gè)整體上對(duì) Netty 有一個(gè)感性的認(rèn)識(shí).
此篇文章涉及的模塊比較多, 面比較廣, 因此寫(xiě)起來(lái)難免有一點(diǎn)跳躍, 并且我感覺(jué)寫(xiě)著寫(xiě)著見(jiàn)見(jiàn)有點(diǎn)不知所云, 邏輯混亂了, 汗. 唉, 還是感覺(jué)自己功力不夠, hold 不住.
接下來(lái)的幾篇文章, 我會(huì)根據(jù) Netty 的各個(gè)模塊深入分析一下, 希望以后的文章能夠組織的調(diào)理更加清晰一些.
本文由 yongshun 發(fā)表于個(gè)人博客, 采用 署名-相同方式共享 3.0 中國(guó)大陸許可協(xié)議.
Email: yongshun1228@gmail.com
本文標(biāo)題為: Netty 源碼分析之 一 揭開(kāi) Bootstrap 神秘的紅蓋頭 (服務(wù)器端)
本文鏈接為: https://segmentfault.com/a/1190000007283053
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/66228.html
摘要:目錄源碼分析之番外篇的前生今世的前生今世之一簡(jiǎn)介的前生今世之二小結(jié)的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環(huán)境搭建源碼分析之一揭開(kāi)神秘的紅蓋頭源碼分析之一揭開(kāi)神秘的紅蓋頭客戶端源碼分析之一揭開(kāi)神秘的紅蓋頭服務(wù)器 目錄 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NIO 的前生今世 之一 簡(jiǎn)介 Java NIO 的前生今世 ...
摘要:背景在工作中雖然我經(jīng)常使用到庫(kù)但是很多時(shí)候?qū)Φ囊恍└拍钸€是處于知其然不知其所以然的狀態(tài)因此就萌生了學(xué)習(xí)源碼的想法剛開(kāi)始看源碼的時(shí)候自然是比較痛苦的主要原因有兩個(gè)第一網(wǎng)上沒(méi)有找到讓我滿意的詳盡的源碼分析的教程第二我也是第一次系統(tǒng)地學(xué)習(xí)這么大代 背景 在工作中, 雖然我經(jīng)常使用到 Netty 庫(kù), 但是很多時(shí)候?qū)?Netty 的一些概念還是處于知其然, 不知其所以然的狀態(tài), 因此就萌生了學(xué)...
摘要:目錄源碼之下無(wú)秘密做最好的源碼分析教程源碼分析之番外篇的前生今世的前生今世之一簡(jiǎn)介的前生今世之二小結(jié)的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環(huán)境搭建源碼分析之一揭開(kāi)神秘的紅蓋頭源碼分析之一揭開(kāi)神秘的紅蓋頭客戶端 目錄 源碼之下無(wú)秘密 ── 做最好的 Netty 源碼分析教程 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NI...
摘要:目錄源碼之下無(wú)秘密做最好的源碼分析教程源碼分析之番外篇的前生今世的前生今世之一簡(jiǎn)介的前生今世之二小結(jié)的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環(huán)境搭建源碼分析之一揭開(kāi)神秘的紅蓋頭源碼分析之一揭開(kāi)神秘的紅蓋頭客戶端 目錄 源碼之下無(wú)秘密 ── 做最好的 Netty 源碼分析教程 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NI...
摘要:目錄源碼之下無(wú)秘密做最好的源碼分析教程源碼分析之番外篇的前生今世的前生今世之一簡(jiǎn)介的前生今世之二小結(jié)的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環(huán)境搭建源碼分析之一揭開(kāi)神秘的紅蓋頭源碼分析之一揭開(kāi)神秘的紅蓋頭客戶端 目錄 源碼之下無(wú)秘密 ── 做最好的 Netty 源碼分析教程 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NI...
閱讀 2060·2019-08-30 15:52
閱讀 2450·2019-08-29 18:37
閱讀 806·2019-08-29 12:33
閱讀 2850·2019-08-29 11:04
閱讀 1546·2019-08-27 10:57
閱讀 2104·2019-08-26 13:38
閱讀 2772·2019-08-26 12:25
閱讀 2460·2019-08-26 12:23