国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Netty 源碼分析之 二 貫穿Netty 的大動脈 ── ChannelPipeline (二)

kamushin233 / 2905人閱讀

摘要:目錄源碼之下無秘密做最好的源碼分析教程源碼分析之番外篇的前生今世的前生今世之一簡介的前生今世之二小結的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環境搭建源碼分析之一揭開神秘的紅蓋頭源碼分析之一揭開神秘的紅蓋頭客戶端

目錄

源碼之下無秘密 ── 做最好的 Netty 源碼分析教程

Netty 源碼分析之 番外篇 Java NIO 的前生今世

Java NIO 的前生今世 之一 簡介

Java NIO 的前生今世 之二 NIO Channel 小結

Java NIO 的前生今世 之三 NIO Buffer 詳解

Java NIO 的前生今世 之四 NIO Selector 詳解

Netty 源碼分析之 零 磨刀不誤砍柴工 源碼分析環境搭建

Netty 源碼分析之 一 揭開 Bootstrap 神秘的紅蓋頭

Netty 源碼分析之 一 揭開 Bootstrap 神秘的紅蓋頭 (客戶端)

Netty 源碼分析之 一 揭開 Bootstrap 神秘的紅蓋頭 (服務器端)

Netty 源碼分析之 二 貫穿 Netty 的大動脈 ── ChannelPipeline (一)

Netty 源碼分析之 二 貫穿 Netty 的大動脈 ── ChannelPipeline (二)

接上篇 Netty 源碼分析之 二 貫穿Netty 的大動脈 ── ChannelPipeline (一)

ChannelHandler 的名字

我們注意到, pipeline.addXXX 都有一個重載的方法, 例如 addLast, 它有一個重載的版本是:

ChannelPipeline addLast(String name, ChannelHandler handler);

第一個參數指定了所添加的 handler 的名字(更準確地說是 ChannelHandlerContext 的名字, 不過我們通常是以 handler 作為敘述的對象, 因此說成 handler 的名字便于理解). 那么 handler 的名字有什么用呢? 如果我們不設置name, 那么 handler 會有怎樣的名字?
為了解答這些疑惑, 老規矩, 依然是從源碼中找到答案.
我們還是以 addLast 方法為例:

@Override
public ChannelPipeline addLast(String name, ChannelHandler handler) {
    return addLast(null, name, handler);
}

這個方法會調用重載的 addLast 方法:

@Override
public ChannelPipeline addLast(EventExecutorGroup group, final String name, ChannelHandler handler) {
    synchronized (this) {
        checkDuplicateName(name);

        AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
        addLast0(name, newCtx);
    }

    return this;
}

第一個參數被設置為 null, 我們不關心它. 第二參數就是這個 handler 的名字. 看代碼可知, 在添加一個 handler 之前, 需要調用 checkDuplicateName 方法來確定此 handler 的名字是否和已添加的 handler 的名字重復. 而這個 checkDuplicateName 方法我們在前面已經有提到, 這里再回顧一下:

private void checkDuplicateName(String name) {
    if (name2ctx.containsKey(name)) {
        throw new IllegalArgumentException("Duplicate handler name: " + name);
    }
}

Netty 判斷一個 handler 的名字是否重復的依據很簡單: DefaultChannelPipeline 中有一個 類型為 Mapname2ctx 字段, 它的 key 是一個 handler 的名字, 而 value 則是這個 handler 所對應的 ChannelHandlerContext. 每當新添加一個 handler 時, 就會 put 到 name2ctx 中. 因此檢查 name2ctx 中是否包含這個 name 即可.
當沒有重名的 handler 時, 就為這個 handler 生成一個關聯的 DefaultChannelHandlerContext 對象, 然后就將 name 和 newCtx 作為 key-value 對 放到 name2Ctx 中.

自動生成 handler 的名字

如果我們調用的是如下的 addLast 方法

ChannelPipeline addLast(ChannelHandler... handlers);

那么 Netty 會調用 generateName 為我們的 handler 自動生成一個名字:

private String generateName(ChannelHandler handler) {
    WeakHashMap, String> cache = nameCaches[(int) (Thread.currentThread().getId() % nameCaches.length)];
    Class handlerType = handler.getClass();
    String name;
    synchronized (cache) {
        name = cache.get(handlerType);
        if (name == null) {
            name = generateName0(handlerType);
            cache.put(handlerType, name);
        }
    }

    synchronized (this) {
        // It"s not very likely for a user to put more than one handler of the same type, but make sure to avoid
        // any name conflicts.  Note that we don"t cache the names generated here.
        if (name2ctx.containsKey(name)) {
            String baseName = name.substring(0, name.length() - 1); // Strip the trailing "0".
            for (int i = 1;; i ++) {
                String newName = baseName + i;
                if (!name2ctx.containsKey(newName)) {
                    name = newName;
                    break;
                }
            }
        }
    }

    return name;
}

而 generateName 會接著調用 generateName0 來實際產生一個 handler 的名字:

private static String generateName0(Class handlerType) {
    return StringUtil.simpleClassName(handlerType) + "#0";
}

自動生成的名字的規則很簡單, 就是 handler 的簡單類名加上 "#0", 因此我們的 EchoClientHandler 的名字就是 "EchoClientHandler#0", 這一點也可以通過調試窗口佐證:

關于 Pipeline 的事件傳輸機制

前面章節中, 我們知道 AbstractChannelHandlerContext 中有 inbound 和 outbound 兩個 boolean 變量, 分別用于標識 Context 所對應的 handler 的類型, 即:

inbound 為真時, 表示對應的 ChannelHandler 實現了 ChannelInboundHandler 方法.

outbound 為真時, 表示對應的 ChannelHandler 實現了 ChannelOutboundHandler 方法.

讀者朋友肯定很疑惑了吧: 那究竟這兩個字段有什么作用呢? 其實這還要從 ChannelPipeline 的傳輸的事件類型說起.
Netty 的事件可以分為 Inbound 和 Outbound 事件.

如下是從 Netty 官網上拷貝的一個圖示:

                                             I/O Request
                                        via Channel or
                                    ChannelHandlerContext
                                                  |
+---------------------------------------------------+---------------+
|                           ChannelPipeline         |               |
|                                                  |/              |
|    +---------------------+            +-----------+----------+    |
|    | Inbound Handler  N  |            | Outbound Handler  1  |    |
|    +----------+----------+            +-----------+----------+    |
|              /|                                  |               |
|               |                                  |/              |
|    +----------+----------+            +-----------+----------+    |
|    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
|    +----------+----------+            +-----------+----------+    |
|              /|                                  .               |
|               .                                   .               |
| ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
|        [ method call]                       [method call]         |
|               .                                   .               |
|               .                                  |/              |
|    +----------+----------+            +-----------+----------+    |
|    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
|    +----------+----------+            +-----------+----------+    |
|              /|                                  |               |
|               |                                  |/              |
|    +----------+----------+            +-----------+----------+    |
|    | Inbound Handler  1  |            | Outbound Handler  M  |    |
|    +----------+----------+            +-----------+----------+    |
|              /|                                  |               |
+---------------+-----------------------------------+---------------+
              |                                  |/
+---------------+-----------------------------------+---------------+
|               |                                   |               |
|       [ Socket.read() ]                    [ Socket.write() ]     |
|                                                                   |
|  Netty Internal I/O Threads (Transport Implementation)            |
+-------------------------------------------------------------------+

從上圖可以看出, inbound 事件和 outbound 事件的流向是不一樣的, inbound 事件的流行是從下至上, 而 outbound 剛好相反, 是從上到下. 并且 inbound 的傳遞方式是通過調用相應的 ChannelHandlerContext.fireIN_EVT() 方法, 而 outbound 方法的的傳遞方式是通過調用 ChannelHandlerContext.OUT_EVT() 方法. 例如 ChannelHandlerContext.fireChannelRegistered() 調用會發送一個 ChannelRegistered 的 inbound 給下一個ChannelHandlerContext, 而 ChannelHandlerContext.bind 調用會發送一個 bind 的 outbound 事件給 下一個 ChannelHandlerContext.

Inbound 事件傳播方法有:

ChannelHandlerContext.fireChannelRegistered()
ChannelHandlerContext.fireChannelActive()
ChannelHandlerContext.fireChannelRead(Object)
ChannelHandlerContext.fireChannelReadComplete()
ChannelHandlerContext.fireExceptionCaught(Throwable)
ChannelHandlerContext.fireUserEventTriggered(Object)
ChannelHandlerContext.fireChannelWritabilityChanged()
ChannelHandlerContext.fireChannelInactive()
ChannelHandlerContext.fireChannelUnregistered()

Oubound 事件傳輸方法有:

ChannelHandlerContext.bind(SocketAddress, ChannelPromise)
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
ChannelHandlerContext.write(Object, ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)

注意, 如果我們捕獲了一個事件, 并且想讓這個事件繼續傳遞下去, 那么需要調用 Context 相應的傳播方法.
例如:

public class MyInboundHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("Connected!");
        ctx.fireChannelActive();
    }
}

public clas MyOutboundHandler extends ChannelOutboundHandlerAdapter {
    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
        System.out.println("Closing ..");
        ctx.close(promise);
    }
}

上面的例子中, MyInboundHandler 收到了一個 channelActive 事件, 它在處理后, 如果希望將事件繼續傳播下去, 那么需要接著調用 ctx.fireChannelActive().

Outbound 操作(outbound operations of a channel)

Outbound 事件都是請求事件(request event), 即請求某件事情的發生, 然后通過 Outbound 事件進行通知.
Outbound 事件的傳播方向是 tail -> customContext -> head.

我們接下來以 connect 事件為例, 分析一下 Outbound 事件的傳播機制.
首先, 當用戶調用了 Bootstrap.connect 方法時, 就會觸發一個 Connect 請求事件, 此調用會觸發如下調用鏈:

Bootstrap.connect -> Bootstrap.doConnect -> Bootstrap.doConnect0 -> AbstractChannel.connect

繼續跟蹤的話, 我們就發現, AbstractChannel.connect 其實由調用了 DefaultChannelPipeline.connect 方法:

@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
    return pipeline.connect(remoteAddress, promise);
}

而 pipeline.connect 的實現如下:

@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
    return tail.connect(remoteAddress, promise);
}

可以看到, 當 outbound 事件(這里是 connect 事件)傳遞到 Pipeline 后, 它其實是以 tail 為起點開始傳播的.
而 tail.connect 其實調用的是 AbstractChannelHandlerContext.connect 方法:

@Override
public ChannelFuture connect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
    ...
    final AbstractChannelHandlerContext next = findContextOutbound();
    EventExecutor executor = next.executor();
    ...
    next.invokeConnect(remoteAddress, localAddress, promise);
    ...
    return promise;
}

findContextOutbound() 顧名思義, 它的作用是以當前 Context 為起點, 向 Pipeline 中的 Context 雙向鏈表的前端尋找第一個 outbound 屬性為真的 Context(即關聯著 ChannelOutboundHandler 的 Context), 然后返回.
它的實現如下:

private AbstractChannelHandlerContext findContextOutbound() {
    AbstractChannelHandlerContext ctx = this;
    do {
        ctx = ctx.prev;
    } while (!ctx.outbound);
    return ctx;
}

當我們找到了一個 outbound 的 Context 后, 就調用它的 invokeConnect 方法, 這個方法中會調用 Context 所關聯著的 ChannelHandler 的 connect 方法:

private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
    try {
        ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
    } catch (Throwable t) {
        notifyOutboundHandlerException(t, promise);
    }
}

如果用戶沒有重寫 ChannelHandler 的 connect 方法, 那么會調用 ChannelOutboundHandlerAdapter 所實現的方法:

@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
        SocketAddress localAddress, ChannelPromise promise) throws Exception {
    ctx.connect(remoteAddress, localAddress, promise);
}

我們看到, ChannelOutboundHandlerAdapter.connect 僅僅調用了 ctx.connect, 而這個調用又回到了:

Context.connect -> Connect.findContextOutbound -> next.invokeConnect -> handler.connect -> Context.connect

這樣的循環中, 直到 connect 事件傳遞到DefaultChannelPipeline 的雙向鏈表的頭節點, 即 head 中. 為什么會傳遞到 head 中呢? 回想一下, head 實現了 ChannelOutboundHandler, 因此它的 outbound 屬性是 true.
因為 head 本身既是一個 ChannelHandlerContext, 又實現了 ChannelOutboundHandler 接口, 因此當 connect 消息傳遞到 head 后, 會將消息轉遞到對應的 ChannelHandler 中處理, 而恰好, head 的 handler() 返回的就是 head 本身:

@Override
public ChannelHandler handler() {
    return this;
}

因此最終 connect 事件是在 head 中處理的. head 的 connect 事件處理方法如下:

@Override
public void connect(
        ChannelHandlerContext ctx,
        SocketAddress remoteAddress, SocketAddress localAddress,
        ChannelPromise promise) throws Exception {
    unsafe.connect(remoteAddress, localAddress, promise);
}

到這里, 整個 Connect 請求事件就結束了.
下面以一幅圖來描述一個整個 Connect 請求事件的處理過程:


點此有無碼高清原圖

我們僅僅以 Connect 請求事件為例, 分析了 Outbound 事件的傳播過程, 但是其實所有的 outbound 的事件傳播都遵循著一樣的傳播規律, 讀者可以試著分析一下其他的 outbound 事件, 體會一下它們的傳播過程.

Inbound 事件

Inbound 事件和 Outbound 事件的處理過程有點鏡像.
Inbound 事件是一個通知事件, 即某件事已經發生了, 然后通過 Inbound 事件進行通知. Inbound 通常發生在 Channel 的狀態的改變或 IO 事件就緒.
Inbound 的特點是它傳播方向是 head -> customContext -> tail.

既然上面我們分析了 Connect 這個 Outbound 事件, 那么接著分析 Connect 事件后會發生什么 Inbound 事件, 并最終找到 Outbound 和 Inbound 事件之間的聯系.

當 Connect 這個 Outbound 傳播到 unsafe 后, 其實是在 AbstractNioUnsafe.connect 方法中進行處理的:

@Override
public final void connect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
    ...
    if (doConnect(remoteAddress, localAddress)) {
        fulfillConnectPromise(promise, wasActive);
    } else {
        ...
    }
    ...
}

在 AbstractNioUnsafe.connect 中, 首先調用 doConnect 方法進行實際上的 Socket 連接, 當連接上后, 會調用 fulfillConnectPromise 方法:

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
    ...
    // Regardless if the connection attempt was cancelled, channelActive() event should be triggered,
    // because what happened is what happened.
    if (!wasActive && isActive()) {
        pipeline().fireChannelActive();
    }
    ...
}

我們看到, 在 fulfillConnectPromise 中, 會通過調用 pipeline().fireChannelActive() 將通道激活的消息(即 Socket 連接成功)發送出去.
而這里, 當調用 pipeline.fireXXX 后, 就是 Inbound 事件的起點.
因此當調用了 pipeline().fireChannelActive() 后, 就產生了一個 ChannelActive Inbound 事件, 我們就從這里開始看看這個 Inbound 事件是怎么傳播的吧.

@Override
public ChannelPipeline fireChannelActive() {
    head.fireChannelActive();

    if (channel.config().isAutoRead()) {
        channel.read();
    }

    return this;
}

哈哈, 果然, 在 fireChannelActive 方法中, 調用的是 head.fireChannelActive, 因此可以證明了, Inbound 事件在 Pipeline 中傳輸的起點是 head.
那么, 在 head.fireChannelActive() 中又做了什么呢?

@Override
public ChannelHandlerContext fireChannelActive() {
    final AbstractChannelHandlerContext next = findContextInbound();
    EventExecutor executor = next.executor();
    ...
    next.invokeChannelActive();
    ...
    return this;
}

上面的代碼應該很熟悉了吧. 回想一下在 Outbound 事件(例如 Connect 事件)的傳輸過程中時, 我們也有類似的操作:

首先調用 findContextInbound, 從 Pipeline 的雙向鏈表中中找到第一個屬性 inbound 為真的 Context, 然后返回

調用這個 Context 的 invokeChannelActive

invokeChannelActive 方法如下:

private void invokeChannelActive() {
    try {
        ((ChannelInboundHandler) handler()).channelActive(this);
    } catch (Throwable t) {
        notifyHandlerException(t);
    }
}

這個方法和 Outbound 的對應方法(例如 invokeConnect) 如出一轍. 同 Outbound 一樣, 如果用戶沒有重寫 channelActive 方法, 那么會調用 ChannelInboundHandlerAdapter 的 channelActive 方法:

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    ctx.fireChannelActive();
}

同樣地, 在 ChannelInboundHandlerAdapter.channelActive 中, 僅僅調用了 ctx.fireChannelActive 方法, 因此就會有如下循環:

Context.fireChannelActive -> Connect.findContextInbound -> nextContext.invokeChannelActive -> nextHandler.channelActive -> nextContext.fireChannelActive

這樣的循環中. 同理, tail 本身 既實現了 ChannelInboundHandler 接口, 又實現了 ChannelHandlerContext 接口, 因此當 channelActive 消息傳遞到 tail 后, 會將消息轉遞到對應的 ChannelHandler 中處理, 而恰好, tail 的 handler() 返回的就是 tail 本身:

@Override
public ChannelHandler handler() {
    return this;
}

因此 channelActive Inbound 事件最終是在 tail 中處理的, 我們看一下它的處理方法:

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { }

TailContext.channelActive 方法是空的. 如果讀者自行查看 TailContext 的 Inbound 處理方法時, 會發現, 它們的實現都是空的. 可見, 如果是 Inbound, 當用戶沒有實現自定義的處理器時, 那么默認是不處理的.

用一幅圖來總結一下 Inbound 的傳輸過程吧:

點擊此可以看高清無碼原圖

總結

對于 Outbound事件:

Outbound 事件是請求事件(由 Connect 發起一個請求, 并最終由 unsafe 處理這個請求)

Outbound 事件的發起者是 Channel

Outbound 事件的處理者是 unsafe

Outbound 事件在 Pipeline 中的傳輸方向是 tail -> head.

在 ChannelHandler 中處理事件時, 如果這個 Handler 不是最后一個 Hnalder, 則需要調用 ctx.xxx (例如 ctx.connect) 將此事件繼續傳播下去. 如果不這樣做, 那么此事件的傳播會提前終止.

Outbound 事件流: Context.OUT_EVT -> Connect.findContextOutbound -> nextContext.invokeOUT_EVT -> nextHandler.OUT_EVT -> nextContext.OUT_EVT

對于 Inbound 事件:

Inbound 事件是通知事件, 當某件事情已經就緒后, 通知上層.

Inbound 事件發起者是 unsafe

Inbound 事件的處理者是 Channel, 如果用戶沒有實現自定義的處理方法, 那么Inbound 事件默認的處理者是 TailContext, 并且其處理方法是空實現.

Inbound 事件在 Pipeline 中傳輸方向是 head -> tail

在 ChannelHandler 中處理事件時, 如果這個 Handler 不是最后一個 Hnalder, 則需要調用 ctx.fireIN_EVT (例如 ctx.fireChannelActive) 將此事件繼續傳播下去. 如果不這樣做, 那么此事件的傳播會提前終止.

Outbound 事件流: Context.fireIN_EVT -> Connect.findContextInbound -> nextContext.invokeIN_EVT -> nextHandler.IN_EVT -> nextContext.fireIN_EVT

outbound 和 inbound 事件十分的鏡像, 并且 Context 與 Handler 直接的調用關系是否容易混淆, 因此讀者在閱讀這里的源碼時, 需要特別的注意.

本文由 yongshun 發表于個人博客, 采用 署名-相同方式共享 3.0 中國大陸許可協議.
Email: yongshun1228@gmail.com
本文標題為: Netty 源碼分析之 二 貫穿Netty 的大動脈 ── ChannelPipeline (二)
本文鏈接為: https://segmentfault.com/a/1190000007309311

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/65216.html

相關文章

  • Netty 源碼分析 貫穿Netty 動脈 ── ChannelPipeline (一)

    摘要:目錄源碼之下無秘密做最好的源碼分析教程源碼分析之番外篇的前生今世的前生今世之一簡介的前生今世之二小結的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環境搭建源碼分析之一揭開神秘的紅蓋頭源碼分析之一揭開神秘的紅蓋頭客戶端 目錄 源碼之下無秘密 ── 做最好的 Netty 源碼分析教程 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NI...

    tunny 評論0 收藏0
  • 源碼下無秘密 ── 做最好 Netty 源碼分析教程

    摘要:背景在工作中雖然我經常使用到庫但是很多時候對的一些概念還是處于知其然不知其所以然的狀態因此就萌生了學習源碼的想法剛開始看源碼的時候自然是比較痛苦的主要原因有兩個第一網上沒有找到讓我滿意的詳盡的源碼分析的教程第二我也是第一次系統地學習這么大代 背景 在工作中, 雖然我經常使用到 Netty 庫, 但是很多時候對 Netty 的一些概念還是處于知其然, 不知其所以然的狀態, 因此就萌生了學...

    shenhualong 評論0 收藏0
  • Netty 源碼分析 三 我就是大名鼎鼎 EventLoop(一)

    摘要:目錄源碼之下無秘密做最好的源碼分析教程源碼分析之番外篇的前生今世的前生今世之一簡介的前生今世之二小結的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環境搭建源碼分析之一揭開神秘的紅蓋頭源碼分析之一揭開神秘的紅蓋頭客戶端 目錄 源碼之下無秘密 ── 做最好的 Netty 源碼分析教程 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NI...

    livem 評論0 收藏0
  • Netty 源碼分析 三 我就是大名鼎鼎 EventLoop()

    摘要:接上篇源碼分析之三我就是大名鼎鼎的一的處理循環在中一個需要負責兩個工作第一個是作為線程負責相應的操作第二個是作為任務線程執行中的任務接下來我們先從操縱方面入手看一下數據是如何從傳遞到我們的中的是模型的一個實現并且是基于的那么從的前生今世之四 接上篇Netty 源碼分析之 三 我就是大名鼎鼎的 EventLoop(一) Netty 的 IO 處理循環 在 Netty 中, 一個 Even...

    whidy 評論0 收藏0
  • Netty4.x 源碼實戰系列(四):Pipeline全剖析

    摘要:在上一篇源碼實戰系列三全剖析中,我們詳細分析了的初始化過程,并得出了如下結論在中,每一個都有一個對象,并且其內部本質上就是一個雙向鏈表本篇我們將深入源碼內部,對其一探究竟,給大家一個全方位解析。 在上一篇《Netty4.x 源碼實戰系列(三):NioServerSocketChannel全剖析》中,我們詳細分析了NioServerSocketChannel的初始化過程,并得出了如下結論...

    13651657101 評論0 收藏0

發表評論

0條評論

kamushin233

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<