Netty剖析 - 2. 實(shí)現(xiàn)

前言

本篇文章主要分析Netty的系統(tǒng)結(jié)構(gòu)以及其如何實(shí)現(xiàn)其對外宣稱的特色,如果還未了解Netty的基礎(chǔ)知識,最好先閱讀本系列的第一篇文章Netty剖析 - 1. 基礎(chǔ)

Netty總體結(jié)構(gòu)

image.png

這張圖摘自Netty官網(wǎng),其展示的是Netty的模塊結(jié)構(gòu),總體來說,Netty分為兩大模塊:

  1. 核心模塊
    核心模塊主要提供的是Netty的一些基礎(chǔ)類和底層接口,主要包含三部分:
    • 用以提升性能,減少資源占用的Zero-Copy-Capable Rich Byte Buffer,即「零拷貝」緩沖區(qū),Netty里的「零拷貝」與操作系統(tǒng)語境下的「零拷貝」不是同一個概念,具體會在后續(xù)章節(jié)做闡述
    • 統(tǒng)一的API,這是Netty對外宣傳的簡單易用API的一部分,什么意思呢?就是Netty為同步和異步IO提供統(tǒng)一的編程接口,舉個例子,如果在前期希望使用BIO,后續(xù)隨著業(yè)務(wù)變動,希望改用NIO,只需要改動幾個簡單的初始化參數(shù),而不需要變動主體流程;相反,如果一開始不是基于Netty,而是直接基于BIO書寫處理流程,后期想改成NIO,其變動是很大的,畢竟是兩個不同的接口模塊
    • 易擴(kuò)展的事件模型,這里的重點(diǎn)在于易擴(kuò)展,因?yàn)镹IO本身就是基于事件的IO模型,而擴(kuò)展性很好理解,如果一個框架無法擴(kuò)展,那么也就意味著無法應(yīng)對業(yè)務(wù)的變化
  2. 服務(wù)模塊
    既然Netty的核心是IO,那么其服務(wù)模塊基本也就和IO操作分不開了,主要有:
    • 網(wǎng)絡(luò)接口數(shù)據(jù)處理相關(guān)服務(wù),如報文的粘包,拆包處理,數(shù)據(jù)的加密,解密等
    • 各網(wǎng)絡(luò)層協(xié)議實(shí)現(xiàn)服務(wù),主要包括傳輸層和應(yīng)用層相關(guān)網(wǎng)絡(luò)協(xié)議的實(shí)現(xiàn)
    • 文件處理相關(guān)服務(wù)

Netty處理架構(gòu)

介紹完Netty的模塊結(jié)構(gòu),我們再來看一下它的處理架構(gòu):


image.png

Netty的架構(gòu)也很清晰,就三層:

  1. 底層IO復(fù)用層,負(fù)責(zé)實(shí)現(xiàn)多路復(fù)用
  2. 通用數(shù)據(jù)處理層,主要對傳輸層的數(shù)據(jù)在進(jìn)和出兩個方向進(jìn)行攔截處理,如編/解碼,粘包處理等
  3. 應(yīng)用實(shí)現(xiàn)層,開發(fā)者在使用Netty的時候基本就在這一層上折騰,同時Netty本身已經(jīng)在這一層提供了一些常用的實(shí)現(xiàn),如HTTP協(xié)議,F(xiàn)TP協(xié)議等

一般來說,數(shù)據(jù)從網(wǎng)絡(luò)傳遞給IO復(fù)用層,IO復(fù)用層收到數(shù)據(jù)后會將數(shù)據(jù)傳遞給上層進(jìn)行處理,這一層會通過一系列的處理Handler以及應(yīng)用服務(wù)對數(shù)據(jù)進(jìn)行處理,然后返回給IO復(fù)用層,通過它再傳回網(wǎng)絡(luò)

基于Reactor模式的IO復(fù)用

在Netty處理架構(gòu)圖中,可以看到在IO復(fù)用層上標(biāo)注了一個「Reactor」:


image.png

這個「Reactor」代表的就是其IO復(fù)用層具體的實(shí)現(xiàn)模式 -- Reactor模式

image.png

這張圖是從大名鼎鼎的Doug Lea的一份演講稿中截取下來的,通過這張圖示,就可以大致明白什么是Reactor模式了。在Reactor模式中,分為主反應(yīng)組(MainReactor)和子反應(yīng)組(subReactor)以及ThreadPool,主反應(yīng)組(MainReactor)負(fù)責(zé)處理連接,連接建立完成以后由主線程對應(yīng)的acceptor將后續(xù)的數(shù)據(jù)處理(read/write)分發(fā)給子反應(yīng)組(subReactor)進(jìn)行處理,而Threadpool對應(yīng)的是業(yè)務(wù)處理線程池;對應(yīng)代碼為:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(1);

ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
            ...

在這段代碼中bossGroup對應(yīng)的就是主反應(yīng)組(MainReactor),workerGroup對應(yīng)的是子反應(yīng)組(subReactor),而NioEventLoopGroup其實(shí)就是一個實(shí)現(xiàn)了Java ExecutorService的線程池,其中的線程數(shù)可定制,若不設(shè)置線程數(shù)參數(shù),則該參數(shù)值默認(rèn)為2 * CPU核數(shù)量,在ServerBootstrap的初始化過程中,會為其添加一個實(shí)現(xiàn)了acceptor機(jī)制的Handler

image.png

而通過ServerBootstrapAcceptor,會在Channel建立后觸發(fā)channelRead()方法,并在channelRead()內(nèi)將此Channel綁定至子反應(yīng)組對應(yīng)的處理線程,后續(xù)的數(shù)據(jù)處理就交于它進(jìn)行處理
image.png

在閱讀這部分源碼的時候需要注意一個點(diǎn),按理來說,連接的建立應(yīng)該是ACCEPT事件,怎么會觸發(fā)channelRead()呢 ?其實(shí)netty內(nèi)部將READACCEPT狀態(tài)一并作為read的觸發(fā)條件
image.png

介紹完了Netty關(guān)于IO復(fù)用層的實(shí)現(xiàn),繼續(xù)看其「易擴(kuò)展」和「關(guān)注點(diǎn)分離」的核心:Pipeline

基于責(zé)任鏈模式的Channel-Pipeline

同樣回過頭去再看Netty處理架構(gòu)圖中的中間層 -- Pipeline


image.png

其字面意思為「管道」,顧名思義,管道的作用就在于傳輸,而對于Netty來說,它的管道傳輸?shù)漠?dāng)然就是數(shù)據(jù)了,如果閱讀過上一篇的讀者應(yīng)該知道,在上一篇關(guān)于Netty基礎(chǔ)的介紹里,提到它有一個很重要的特色就在于:基于事件機(jī)制(Pipeline - Handler)達(dá)成關(guān)注點(diǎn)分離(消息編解碼,協(xié)議編解碼,業(yè)務(wù)處理),而Pipeline就是實(shí)現(xiàn)這一特色的核心所在,我們下面來看Netty是如何實(shí)現(xiàn)所謂的「易擴(kuò)展」和「關(guān)注點(diǎn)分離」的

首先,Netty的Pipeline從數(shù)據(jù)傳輸?shù)姆较蛏蟻砜捶譃檫M(jìn)和出,這個和BIO相同;其次,最重要的在于Netty在Pipeline上通過責(zé)任鏈模式插入一系列的「Handler」,這一結(jié)構(gòu)是它能實(shí)現(xiàn)「易擴(kuò)展」和「關(guān)注點(diǎn)分離」的關(guān)鍵。想想看,所謂IO,不就是數(shù)據(jù)的「進(jìn)」和「出」嗎?而進(jìn)來干啥呢?當(dāng)然就是需要應(yīng)用邏輯對其處理,那處理完了呢?還需要送回給請求方以示響應(yīng),而在進(jìn)的過程中需要哪些處理邏輯,這些處理邏輯的先后順序如何,處理完后出去的過程中需要哪些處理邏輯,這些處理邏輯的順序又是如何,如果這些都可以方便的配置調(diào)整,是不是就達(dá)到了Netty宣稱的「易擴(kuò)展」和「關(guān)注點(diǎn)分離」(只需關(guān)注業(yè)務(wù)相關(guān)的Handler,網(wǎng)絡(luò)協(xié)議相關(guān)的Handler直接調(diào)用即可,IO復(fù)用更無須關(guān)注)呢?

在Netty里,這一實(shí)現(xiàn)機(jī)制的核心類叫做ChannelPipeline

image.png

其中Channel負(fù)責(zé)數(shù)據(jù)通信,Handler負(fù)責(zé)邏輯處理,而ChannelPipeline就相當(dāng)于一個由Handler串起來的處理鏈條,在Neety源碼里有一個關(guān)于ChannelPipeline的比較形象的圖形化描述:
image.png

看到?jīng)]有,其實(shí)很簡單,就是一個針對不同方向數(shù)據(jù)流的責(zé)任鏈,其中Inbound對應(yīng)的是輸入流,Outbound對應(yīng)的是輸出流(在這里再多提一句,責(zé)任鏈模式在很多框架里都有使用,比如Spring MVC里看到的各種Handler,也是基于責(zé)任鏈的封裝)

強(qiáng)大的ByteBuf

既然是對Netty進(jìn)行分析,就必然繞不過Netty自己封裝的數(shù)據(jù)緩沖區(qū):ByteBuf,它是Netty對外宣稱的高性能的重要支撐,另外有必要提一下,在Netty里其核心緩沖區(qū)類叫「ByteBuf」,以便與NIO本身的緩沖區(qū)類「ByteBuffer」做區(qū)分,ByteBuf有如下特點(diǎn):

  • 功能豐富的接口,Java NIO本身的緩沖區(qū)接口比較簡單
  • 支持零拷貝,提升性能,減少資源占用
  • 支持動態(tài)擴(kuò)展
  • 緩沖區(qū)初始塊大小動態(tài)控制
  • 讀寫切換不需要手動調(diào)用clear(),flip();使用過Java NIO的小伙伴應(yīng)該知道,其在進(jìn)行讀寫切換時需要不停的通過clear()和flip()進(jìn)行模式切換,很麻煩
  • 池化,提升性能,減少資源占用

下面將對上面提到的幾個ByteBuf的重要特性進(jìn)行實(shí)現(xiàn)分析,首先來看下ByteBuf是如何避免NIO中那繁瑣的讀寫切換的。我們知道,對于Java NIO的Buffer,其有幾個重要的屬性:positionlimit,capacity,其中position代表的是下一個讀或?qū)懙奈恢茫?code>limit是可被讀或?qū)懙淖罡呶?,?code>capacity就是Buffer的容量了,之所以要在讀和寫切換的時候進(jìn)行手動操作(clear(),flip()),主要是因?yàn)樵贜IO中,positionlimit在讀的時候代表的是下一個需讀的位和可讀的最高位,但是在寫的時候又代表下一個需寫的位和可寫的最高位(其實(shí)就是capacity),換句話說這兩個變量在不同的操作場景下有不同的含義,對應(yīng)值也不同,所以需要在讀寫切換的時候進(jìn)行手動操作

image.png

而Netty的ByteBuf則對這一點(diǎn)做了改進(jìn),其針對讀寫操作分別增加上了readerIndex,writerIndex,使用的時候不需要考慮讀寫轉(zhuǎn)換
image.png

讀的時候就變動readerIndex的值,而此時可讀的最高位(對應(yīng)NIO中的limit)其實(shí)就是writerIndex,同理寫的時候就變動writerIndex,此時可寫的最高位(對應(yīng)NIO中的limit)就是capacity,說白了就是兩個變量分別管理讀和寫的操作位,互不沖突,也就不存在讀寫切換的時候手動操作了;其實(shí)看到這里我們可以發(fā)現(xiàn),NIO在接口設(shè)計的時候確實(shí)沒有考慮周到,畢竟Netty的這種優(yōu)化并不是有多難!

零拷貝Buf

在分析ByteBuf的「零拷貝」特性之前,先說說什么是「零拷貝」,所謂「零拷貝」, 通常指的是在 OS 層面上為了避免在用戶態(tài)(User-space) 與 內(nèi)核態(tài)(Kernel-space) 之間進(jìn)行數(shù)據(jù)拷貝而采取的性能優(yōu)化措施;例如 Linux 提供的 mmap 系統(tǒng)調(diào)用,它可以將一段用戶空間內(nèi)存映射到內(nèi)核空間,當(dāng)映射成功后,用戶對這段內(nèi)存區(qū)域的修改可以直接反映到內(nèi)核空間;同樣地,內(nèi)核空間對這段區(qū)域的修改也直接反映用戶空間。正因?yàn)橛羞@樣的映射關(guān)系,我們就不需要在用戶態(tài)(User-space) 與內(nèi)核態(tài)(Kernel-space) 之間拷貝數(shù)據(jù),從而提高了數(shù)據(jù)傳輸?shù)男?;對于Java的網(wǎng)絡(luò)操作來說,網(wǎng)絡(luò)接口在收到數(shù)據(jù)的時候需要先將數(shù)據(jù)復(fù)制到內(nèi)核內(nèi)存,然后在從內(nèi)核內(nèi)存復(fù)制到用戶內(nèi)存,同理往網(wǎng)絡(luò)接口發(fā)數(shù)據(jù)也是先將數(shù)據(jù)從用戶內(nèi)存復(fù)制到內(nèi)核內(nèi)存,再從內(nèi)核內(nèi)存中將數(shù)據(jù)傳給網(wǎng)絡(luò)接口,所以如果是直接操縱內(nèi)核內(nèi)存,無疑處理的性能會更好

回到Netty,Netty中的 「零拷貝」與上面我們所提到到 OS 層面上的 「零拷貝」其實(shí)不太一樣,Netty的 「零拷貝」 完全是在用戶態(tài)里的,或者說更多的是偏向于減少JVM內(nèi)的數(shù)據(jù)操作,具體體現(xiàn)在如下幾個方面:

  • 通過 CompositeByteBuf類,將多個ByteBuf合并為一個邏輯上的ByteBuf,避免了各個ByteBuf之間的拷貝
  • 通過wrap操作,將byte[]數(shù)組、ByteBuf、ByteBuffer等多個數(shù)據(jù)容器合并成一個ByteBuf對象,進(jìn)而避免了拷貝操作
  • 通過slice操作,將ByteBuf分解為多個共享同一個存儲區(qū)域的ByteBuf,避免了內(nèi)存的拷貝
  • 通過FileRegion包裝的FileChannel.tranferTo實(shí)現(xiàn)文件傳輸,將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo)Channel,避免了傳統(tǒng)通過循環(huán)write方式導(dǎo)致的內(nèi)存拷貝問題

這些操作之所以能避免不必要的拷貝操作,其實(shí)就在于內(nèi)部對數(shù)據(jù)進(jìn)行的是邏輯操作而非物理操作,操作完成后根據(jù)各邏輯引用的數(shù)據(jù)信息(大小,位置等)重新計算ByteBuf內(nèi)部的控制屬性(limit,capacity,readerIndex,writerIndex),如通過CompositeByteBuf將原本兩個分別表示head和body的buffer組裝成一個buffer:

image.png

雖然看起來CompositeByteBuf是由兩個ByteBuf組合而成的,不過在CompositeByteBuf內(nèi)部,這兩個ByteBuf都是單獨(dú)存在的(指針引用),CompositeByteBuf只是邏輯上是一個整體;這樣在數(shù)據(jù)操作的時候不需要對數(shù)據(jù)進(jìn)行物理挪動,只需要操作數(shù)據(jù)引用并計算關(guān)鍵因子即可,這種方式不但能提升性能,還可以減少內(nèi)存占用,值得借鑒

Buf池化

在Netty中,ByteBuf用來作為數(shù)據(jù)的容器,是一種會被頻繁創(chuàng)建和銷毀的對象,ByteBuf需要的內(nèi)存空間,可以在 JVM Heap 中申請分配,也可以在Direct Memory(堆外內(nèi)存)中申請,其中在 Direct Memory 中分配的ByteBuf,其創(chuàng)建和銷毀的代價比在 JVM Heap 中的更高,但拋開哪個代價高哪個代價低不說,光是頻繁創(chuàng)建和頻繁銷毀這一點(diǎn),就已奠定了效率不高的基調(diào)。Netty為了解決這個問題,引入了池化技術(shù),池化技術(shù)的思想不復(fù)雜,和線程池思想類似,說白了就是對一些可重用的對象用完不回收,后面需要再次使用,以減少創(chuàng)建和銷毀對象帶來的資源損耗,下面結(jié)合Netty源碼對其池化技術(shù)做剖析

首先看ByteBuf,它實(shí)現(xiàn)了ReferenceCounted接口,表明該類是一個引用計數(shù)管理對象

public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf>

而引用計數(shù)就是實(shí)現(xiàn)池化的關(guān)鍵技術(shù)點(diǎn)(不過并非只有池化的 ByteBuf 才有引用計數(shù),非池化的也會有引用),繼續(xù)看ReferenceCounted接口,它定義了這幾個方法:

public interface ReferenceCounted {
    int refCnt();

    ReferenceCounted retain();

    ReferenceCounted retain(int increment);

    boolean release();

    boolean release(int decrement);
}

每一個引用計數(shù)對象,都維護(hù)了一個自身的引用計數(shù),當(dāng)?shù)谝淮伪粍?chuàng)建時,引用計數(shù)為1,通過refCnt()方法可以得到當(dāng)前的引用計數(shù),retain()retain(int increment)增加自身的引用計數(shù)值,而release()release(int increment)則減少當(dāng)前的引用計數(shù)值,如果引用計數(shù)值為 0,并且當(dāng)前的 ByteBuf 被釋放成功,那這兩個方法的返回值就為true。而具體如何釋放,各種不同類型的ByteBuf自己決定,如果是池化的ByteBuf,那么就會重新進(jìn)池子,以待重用;如果是非池化的,則銷毀底層的字節(jié)數(shù)組引用或者釋放對應(yīng)的堆外內(nèi)存。具體的邏輯在AbstractReferenceCountedByteBuf類中可以看到:

    @Override
    public final boolean release() {
        for (;;) {
            int refCnt = this.refCnt;
            if (refCnt == 0) {
                throw new IllegalReferenceCountException(0, -1);
            }

            if (refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)) {
                if (refCnt == 1) {
                    deallocate();
                    return true;
                }
                return false;
            }
        }
    }

釋放對象的方法定義在 deallocate() 方法里,它是個抽象方法,既然是抽象的,那么就需要子類自行實(shí)現(xiàn),對于非池化的 HeapByteBuf 來說,釋放對象實(shí)際上就是釋放底層字節(jié)數(shù)組的引用:

    @Override
    protected void deallocate() {
        array = null;
    }

對于非池化的DirectByteBuf來說,釋放對象實(shí)際上就是釋放堆外內(nèi)存:

    @Override
    protected void deallocate() {
        ByteBuffer buffer = this.buffer;
        if (buffer == null) {
            return;
        }

        this.buffer = null;

        if (!doNotFree) {
            PlatformDependent.freeDirectBuffer(buffer);
        }

        if (leak != null) {
            leak.close();
        }
    }

對于池化的 ByteBuf 來說,就是把自己歸還到對象池里:

    @Override
    protected final void deallocate() {
        if (handle >= 0) {
            final long handle = this.handle;
            this.handle = -1;
            memory = null;
            chunk.arena.free(chunk, handle);
            if (leak != null) {
                leak.close();
            } else {
                recycle();
            }
        }
    }

熟悉JVM GC的同學(xué)應(yīng)該對這個引用計數(shù)的機(jī)制不會感到陌生,因?yàn)镴VM在判斷一個Java對象是否存活時有一種方式使用的就是計數(shù)法;另外Netty的池化緩存在實(shí)現(xiàn)上借鑒了buddy allocation和slab allocation的思想并進(jìn)行了比較復(fù)雜的設(shè)計(buddy allocation是基于一定規(guī)則對內(nèi)存進(jìn)行分割,回收時進(jìn)行合并,盡可能保證系統(tǒng)有足夠的連續(xù)內(nèi)存;而slab allocation是把內(nèi)存分割為大小不等的內(nèi)存塊,請求內(nèi)存是分配最貼近請求size的內(nèi)存塊,避免內(nèi)存浪費(fèi)),可以減少對象的創(chuàng)建與銷毀對性能的影響,因?yàn)榫彌_區(qū)對象的創(chuàng)建與銷毀會占用內(nèi)存帶寬以及GC資源,另外由于池化緩存本身比較復(fù)雜,如線程私有池與全局共有池,其聲明與釋放都需要手動處理(比如本地池內(nèi)的緩沖區(qū)對象如果不是在同一個線程內(nèi)釋放就會導(dǎo)致內(nèi)存泄漏,這也是為什么JVM GC的時候需要有Stop The World),Netty提供了內(nèi)存泄漏監(jiān)控工具ResourceLeakDetector,如果發(fā)生了內(nèi)存泄漏,它會通過日志記錄并提醒,這個工具主要是防止對象被GC的時候其占用的資源沒有被釋放(如內(nèi)存),或者沒有執(zhí)行release方法

也許有人會說,既然池化緩存實(shí)現(xiàn)復(fù)雜,用起來還得防止內(nèi)存泄漏,那么它到底能給性能帶來多大提升呢?我們可以看下Twitter對Netty池化緩存做的性能測試結(jié)果:


image.png

這張圖的Y軸顯示的是創(chuàng)建對象花費(fèi)的時間,而X軸代表的是所創(chuàng)建對象的大小,同時在實(shí)驗(yàn)中,使用了四種不同的對象,分別是非池化堆內(nèi)存對象(Unpooled Heap),池化堆內(nèi)存對象(Pooled Heap),非池化直接內(nèi)存對象(Unpooled Direct),池化直接內(nèi)存對象(Pooled Direct)。結(jié)果現(xiàn)實(shí),隨著被創(chuàng)建對象大小的增加,池化技術(shù)的優(yōu)勢愈加明顯,當(dāng)然當(dāng)對象很小時,池化反而不如JVM本身的對象創(chuàng)建性能(可以結(jié)合ByteBuf的實(shí)現(xiàn)原理,想想為什么?)

除了對象創(chuàng)建的性能,Twitter還測試了使用池化技術(shù)時GC相關(guān)的表現(xiàn),實(shí)驗(yàn)?zāi)M了在16000個連接下,對256byte大小的數(shù)據(jù)包進(jìn)行循環(huán)傳輸:


image.png

結(jié)果表明,相對于非池化,池化的GC停頓減少了近4倍,而垃圾的增長也慢了4倍。所以說,Netty對ByteBuf進(jìn)行的復(fù)雜的重寫還是值得的

NIO epoll死循環(huán)問題及Netty解決方案

最后說說Netty是如何解決著名的「NIO epoll死循環(huán)」問題的。什么是「NIO epoll死循環(huán)」呢?在Linux系統(tǒng)中,當(dāng)某個socket的連接突然中斷后,會重設(shè)事件集eventSet,而eventSet的重設(shè)就會導(dǎo)致Selector被喚醒(但其實(shí)這個時候是沒有任何事件需要處理的,select()方法應(yīng)該還是處于阻塞狀態(tài)),雖然被喚醒了,但其實(shí)是沒有事件需要處理的,所以就又返回select()方法之前(正常情況下是處理完事件重新回去被select()阻塞),此時select()方法還是會直接返回,如此反復(fù)便造成死循環(huán):

image.png

這個問題的原因本質(zhì)上就是NIO的Selector實(shí)現(xiàn)有問題,Netty解決的方式其實(shí)比較簡單粗暴,它會記錄一段時間內(nèi)空輪詢的次數(shù),如果超過一定閾值,就認(rèn)為這個bug出現(xiàn)了,此時會重新生成一個新的selector取代舊的selector,避免死循環(huán),具體的處理代碼在NioEventLoop中:
image.png

Netty主要類關(guān)系圖

這里貼一張Netty主要實(shí)現(xiàn)類的關(guān)系圖,對需要閱讀Netty源碼的小伙伴可能有一個參考作用


image.png

總結(jié)

本篇主要介紹了Netty的總體架構(gòu),并對Netty的一些重要的實(shí)現(xiàn)機(jī)制進(jìn)行了簡單的剖析,如果在閱讀本篇時發(fā)現(xiàn)對一些基礎(chǔ)的概念和知識不是很了解,可以閱讀本系列的第一篇Netty剖析 - 1. 基礎(chǔ)進(jìn)行相關(guān)學(xué)習(xí),如果希望對Netty的實(shí)現(xiàn)有更深入的了解,推薦去Netty的官網(wǎng),或者閱讀Netty源碼,如果還希望了解Netty其他相關(guān)知識,也可以閱讀本系列的最后一篇文章Netty剖析 - 3. 總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、Netty基礎(chǔ)入門 Netty是由JBOSS提供的一個java開源框架。Netty提供異步的、事件驅(qū)動的網(wǎng)絡(luò)應(yīng)...
    我是嘻哈大哥閱讀 4,793評論 0 31
  • 作者:李林鋒 原文:http://www.infoq.com/cn/articles/netty-high-per...
    楊鑫科閱讀 4,119評論 0 64
  • 前奏 https://tech.meituan.com/2016/11/04/nio.html 綜述 netty通...
    jiangmo閱讀 6,224評論 0 13
  • 頭先出,開啟了漫長人生; 腳先硬,踏遍了大千世界; 臉先長,顯示了飽經(jīng)滄桑; 心先涼,透出了生活現(xiàn)實(shí)。
    T1M_c687閱讀 264評論 0 0
  • 公司準(zhǔn)備召開檢討會要求對同事提出意見,下面是職員A,B和領(lǐng)導(dǎo)C的對話。 A:怎么提意見又不得罪人?。?B:這好辦啊...
    hello0905閱讀 307評論 0 2

友情鏈接更多精彩內(nèi)容