自頂向下深入分析Netty(六)--Channel總述

Netty架構(gòu)模式

回顧這幅圖,目前為止,我們明白了兩個Reactor、acceptor以及異步結(jié)果的原理。在這一章中,我們將分析圖中的箭頭部分,將各部件連接起來。進行連接的關(guān)鍵部件正是本章的主角Channel,Channel是網(wǎng)絡Socket進行聯(lián)系的紐帶,可以完成諸如讀、寫、連接、綁定等I/O操作。

6.1 總述

6.1.1 Channel

JDK中的Channel是通訊的載體,而Netty中的Channel在此基礎上進行封裝從而賦予了Channel更多的能力,用戶可以使用Channel進行以下操作:

  • 查詢Channel的狀態(tài)。
  • 配置Channel的參數(shù)。
  • 進行Channel支持的I/O操作(read,write,connect,bind)。
  • 獲取對應的ChannelPipeline,從而可以自定義處理I/O事件或者其他請求。

為了保證在使用Channel或者處理I/O操作時不出現(xiàn)錯誤,以下幾點需要特別注意:

  1. 所有的I/O操作都是異步的
    由于采用事件驅(qū)動的機制,所以Netty中的所有IO操作都是異步的。這意味著當我們調(diào)用一個IO操作時,方法會立即返回并不保證操作已經(jīng)完成。由上一章Future的講解中,我們知道,這些IO操作會返回一個ChannelFuture對象,我們需要通過添加監(jiān)聽者的方式執(zhí)行操作完成后需執(zhí)行的代碼。
  2. Channel是有等級的
    如果一個Channel由另一個Channel創(chuàng)建,那么他們之間形成父子關(guān)系。比如說,當ServerSocketChannel通過accept()方法接受一個SocketChannel時,那么SocketChannel的父親是ServerSocketChannel,調(diào)用SocketChannel的parent()方法返回該ServerSocketChannel對象。
  3. 可以使用向下轉(zhuǎn)型獲取子類的特定操作
    某些子類Channel會提供一些所需的特定操作,可以向下轉(zhuǎn)型到這樣的子類,從而獲得特定操作。比如說,對于UDP的數(shù)據(jù)報的傳輸,有特定的join()和leave()操作,我們可以向下轉(zhuǎn)型到DatagramChannel從而使用這些操作。
  4. 釋放資源
    當一個Channel不再使用時,須調(diào)用close()或者close(ChannelPromise)方法釋放資源。

6.1.2 Channel配置參數(shù)

(1).通用參數(shù)

CONNECT_TIMEOUT_MILLIS
????????Netty參數(shù),連接超時毫秒數(shù),默認值30000毫秒即30秒。

MAX_MESSAGES_PER_READ
????????Netty參數(shù),一次Loop讀取的最大消息數(shù),對于ServerChannel或者NioByteChannel,默認值為16,其他Channel默認值為1。默認值這樣設置,是因為:ServerChannel需要接受足夠多的連接,保證大吞吐量,NioByteChannel可以減少不必要的系統(tǒng)調(diào)用select。

WRITE_SPIN_COUNT
????????Netty參數(shù),一個Loop寫操作執(zhí)行的最大次數(shù),默認值為16。也就是說,對于大數(shù)據(jù)量的寫操作至多進行16次,如果16次仍沒有全部寫完數(shù)據(jù),此時會提交一個新的寫任務給EventLoop,任務將在下次調(diào)度繼續(xù)執(zhí)行。這樣,其他的寫請求才能被響應不會因為單個大數(shù)據(jù)量寫請求而耽誤。

ALLOCATOR
????????Netty參數(shù),ByteBuf的分配器,默認值為ByteBufAllocator.DEFAULT,4.0版本為UnpooledByteBufAllocator,4.1版本為PooledByteBufAllocator。該值也可以使用系統(tǒng)參數(shù)io.netty.allocator.type配置,使用字符串值:"unpooled","pooled"。

RCVBUF_ALLOCATOR
????????Netty參數(shù),用于Channel分配接受Buffer的分配器,默認值為AdaptiveRecvByteBufAllocator.DEFAULT,是一個自適應的接受緩沖區(qū)分配器,能根據(jù)接受到的數(shù)據(jù)自動調(diào)節(jié)大小。可選值為FixedRecvByteBufAllocator,固定大小的接受緩沖區(qū)分配器。

AUTO_READ
????????Netty參數(shù),自動讀取,默認值為True。Netty只在必要的時候才設置關(guān)心相應的I/O事件。對于讀操作,需要調(diào)用channel.read()設置關(guān)心的I/O事件為OP_READ,這樣若有數(shù)據(jù)到達才能讀取以供用戶處理。該值為True時,每次讀操作完畢后會自動調(diào)用channel.read(),從而有數(shù)據(jù)到達便能讀取;否則,需要用戶手動調(diào)用channel.read()。需要注意的是:當調(diào)用config.setAutoRead(boolean)方法時,如果狀態(tài)由false變?yōu)閠rue,將會調(diào)用channel.read()方法讀取數(shù)據(jù);由true變?yōu)閒alse,將調(diào)用config.autoReadCleared()方法終止數(shù)據(jù)讀取。

WRITE_BUFFER_HIGH_WATER_MARK
????????Netty參數(shù),寫高水位標記,默認值64KB。如果Netty的寫緩沖區(qū)中的字節(jié)超過該值,Channel的isWritable()返回False。

WRITE_BUFFER_LOW_WATER_MARK
????????Netty參數(shù),寫低水位標記,默認值32KB。當Netty的寫緩沖區(qū)中的字節(jié)超過高水位之后若下降到低水位,則Channel的isWritable()返回True。寫高低水位標記使用戶可以控制寫入數(shù)據(jù)速度,從而實現(xiàn)流量控制。推薦做法是:每次調(diào)用channl.write(msg)方法首先調(diào)用channel.isWritable()判斷是否可寫。

MESSAGE_SIZE_ESTIMATOR
????????Netty參數(shù),消息大小估算器,默認為DefaultMessageSizeEstimator.DEFAULT。估算ByteBuf、ByteBufHolder和FileRegion的大小,其中ByteBuf和ByteBufHolder為實際大小,F(xiàn)ileRegion估算值為0。該值估算的字節(jié)數(shù)在計算水位時使用,F(xiàn)ileRegion為0可知FileRegion不影響高低水位。

SINGLE_EVENTEXECUTOR_PER_GROUP
????????Netty參數(shù),單線程執(zhí)行ChannelPipeline中的事件,默認值為True。該值控制執(zhí)行ChannelPipeline中執(zhí)行ChannelHandler的線程。如果為Trye,整個pipeline由一個線程執(zhí)行,這樣不需要進行線程切換以及線程同步,是Netty4的推薦做法;如果為False,ChannelHandler中的處理過程會由Group中的不同線程執(zhí)行。

(2).SocketChannel參數(shù)

SO_RCVBUF
????????Socket參數(shù),TCP數(shù)據(jù)接收緩沖區(qū)大小。該緩沖區(qū)即TCP接收滑動窗口,linux操作系統(tǒng)可使用命令:cat /proc/sys/net/ipv4/tcp_rmem查詢其大小。一般情況下,該值可由用戶在任意時刻設置,但當設置值超過64KB時,需要在連接到遠端之前設置。

SO_SNDBUF
????????Socket參數(shù),TCP數(shù)據(jù)發(fā)送緩沖區(qū)大小。該緩沖區(qū)即TCP發(fā)送滑動窗口,linux操作系統(tǒng)可使用命令:cat /proc/sys/net/ipv4/tcp_smem查詢其大小。

TCP_NODELAY
????????TCP參數(shù),立即發(fā)送數(shù)據(jù),默認值為Ture(Netty默認為True而操作系統(tǒng)默認為False)。該值設置Nagle算法的啟用,改算法將小的碎片數(shù)據(jù)連接成更大的報文來最小化所發(fā)送的報文的數(shù)量,如果需要發(fā)送一些較小的報文,則需要禁用該算法。Netty默認禁用該算法,從而最小化報文傳輸延時。

SO_KEEPALIVE
????????Socket參數(shù),連接保活,默認值為False。啟用該功能時,TCP會主動探測空閑連接的有效性。可以將此功能視為TCP的心跳機制,需要注意的是:默認的心跳間隔是7200s即2小時。Netty默認關(guān)閉該功能。

SO_REUSEADDR
????????Socket參數(shù),地址復用,默認值False。有四種情況可以使用:(1).當有一個有相同本地地址和端口的socket1處于TIME_WAIT狀態(tài)時,而你希望啟動的程序的socket2要占用該地址和端口,比如重啟服務且保持先前端口。(2).有多塊網(wǎng)卡或用IP Alias技術(shù)的機器在同一端口啟動多個進程,但每個進程綁定的本地IP地址不能相同。(3).單個進程綁定相同的端口到多個socket上,但每個socket綁定的ip地址不同。(4).完全相同的地址和端口的重復綁定。但這只用于UDP的多播,不用于TCP。

SO_LINGER
???????? Netty對底層Socket參數(shù)的簡單封裝,關(guān)閉Socket的延遲時間,默認值為-1,表示禁用該功能。-1以及所有<0的數(shù)表示socket.close()方法立即返回,但OS底層會將發(fā)送緩沖區(qū)全部發(fā)送到對端。0表示socket.close()方法立即返回,OS放棄發(fā)送緩沖區(qū)的數(shù)據(jù)直接向?qū)Χ税l(fā)送RST包,對端收到復位錯誤。非0整數(shù)值表示調(diào)用socket.close()方法的線程被阻塞直到延遲時間到或發(fā)送緩沖區(qū)中的數(shù)據(jù)發(fā)送完畢,若超時,則對端會收到復位錯誤。

IP_TOS
????????IP參數(shù),設置IP頭部的Type-of-Service字段,用于描述IP包的優(yōu)先級和QoS選項。

ALLOW_HALF_CLOSURE
????????Netty參數(shù),一個連接的遠端關(guān)閉時本地端是否關(guān)閉,默認值為False。值為False時,連接自動關(guān)閉;為True時,觸發(fā)ChannelInboundHandler的userEventTriggered()方法,事件為ChannelInputShutdownEvent。

(3).ServerSocketChannel參數(shù)

SO_RCVBUF
????????已說明,需要注意的是:當設置值超過64KB時,需要在綁定到本地端口前設置。該值設置的是由ServerSocketChannel使用accept接受的SocketChannel的接收緩沖區(qū)。

SO_REUSEADDR
????????已說明

SO_BACKLOG
????????Socket參數(shù),服務端接受連接的隊列長度,如果隊列已滿,客戶端連接將被拒絕。默認值,Windows為200,其他為128。

(4).DatagramChannel參數(shù)

SO_BROADCAST
????????Socket參數(shù),設置廣播模式。

SO_RCVBUF
????????已說明

SO_SNDBUF
????????已說明

SO_REUSEADDR
????????已說明

IP_MULTICAST_LOOP_DISABLED
????????對應IP參數(shù)IP_MULTICAST_LOOP,設置本地回環(huán)接口的多播功能。由于IP_MULTICAST_LOOP返回True表示關(guān)閉,所以Netty加上后綴_DISABLED防止歧義。

IP_MULTICAST_ADDR
????????對應IP參數(shù)IP_MULTICAST_IF,設置對應地址的網(wǎng)卡為多播模式。

IP_MULTICAST_IF
????????對應IP參數(shù)IP_MULTICAST_IF2,同上但支持IPV6。

IP_MULTICAST_TTL
????????IP參數(shù),多播數(shù)據(jù)報的time-to-live即存活跳數(shù)。

IP_TOS
????????已說明

DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION
????????Netty參數(shù),DatagramChannel注冊的EventLoop即表示已激活。


6.1.3 Channel接口

Channel接口中含有大量的方法,我們先對這些方法分類:

  1. 狀態(tài)查詢
    boolean isOpen(); // 是否開放
    boolean isRegistered(); // 是否注冊到一個EventLoop
    boolean isActive(); // 是否激活
    boolean isWritable();   // 是否可寫

open表示Channel的開放狀態(tài),True表示Channel可用,F(xiàn)alse表示Channel已關(guān)閉不再可用。registered表示Channel的注冊狀態(tài),True表示已注冊到一個EventLoop,F(xiàn)alse表示沒有注冊到EventLoop。active表示Channel的激活狀態(tài),對于ServerSocketChannel,True表示Channel已綁定到端口;對于SocketChannel,表示Channel可用(open)且已連接到對端。Writable表示Channel的可寫狀態(tài),當Channel的寫緩沖區(qū)outboundBuffer非null且可寫時返回True。
一個正常結(jié)束的Channel狀態(tài)轉(zhuǎn)移有以下兩種情況:

    REGISTERED->CONNECT/BIND->ACTIVE->CLOSE->INACTIVE->UNREGISTERED 
    REGISTERED->ACTIVE->CLOSE->INACTIVE->UNREGISTERED

其中第一種是服務端用于綁定的Channel或者客戶端用于發(fā)起連接的Channel,第二種是服務端接受的SocketChannel。一個異常關(guān)閉的Channel則不會服從這樣的狀態(tài)轉(zhuǎn)移。

  1. getter方法
    EventLoop eventLoop();  // 注冊到的EventLoop
    Channel parent();   // 父類Channel
    ChannelConfig config(); // 配置參數(shù)
    ChannelMetadata metadata(); // 元數(shù)據(jù)
    SocketAddress localAddress();   // 本地地址
    SocketAddress remoteAddress();  // 遠端地址
    Unsafe unsafe();    // Unsafe對象
    ChannelPipeline pipeline(); // 事件管道,用于處理IO事件
    ByteBufAllocator alloc();   // 字節(jié)緩存分配器
    ChannelFuture closeFuture();    // Channel關(guān)閉時的異步結(jié)果
    ChannelPromise voidPromise();   
  1. 異步結(jié)果生成
    ChannelPromise newPromise();
    ChannelFuture newSucceededFuture();
    ChannelFuture newFailedFuture(Throwable cause);
  1. I/O事件處理
    ChannelFuture bind(SocketAddress localAddress);
    ChannelFuture connect(SocketAddress remoteAddress);
    ChannelFuture disconnect();
    ChannelFuture close();
    ChannelFuture deregister();
    Channel read();
    ChannelFuture write(Object msg);
    Channel flush();
    ChannelFuture writeAndFlush(Object msg);

這里的I/O事件都是outbound出站事件,表示由用戶發(fā)起,即用戶可以調(diào)用這些方法產(chǎn)生響應的事件。對應地,有inbound入站事件,將在ChnanelPipeline一節(jié)中詳述。


6.1.4 Unsafe

Unsafe?直譯中文為不安全,這曾給我?guī)順O大的困擾。如果你是第一次遇到這種接口,一定會和我感同身受。一個Unsafe對象是不安全的?這里說的不安全,是相對于用戶程序員而言的,也就是說,用戶程序員使用Netty進行編程時不會接觸到這個接口和相關(guān)類。為什么不會接觸到呢?因為類似的接口和類是Netty的大量內(nèi)部實現(xiàn)細節(jié),不會暴露給用戶程序員。然而我們的目標是自頂向下深入分析Netty,所以有必要深入Unsafe雷區(qū)。我們先看Unsafe接口中的方法:

    SocketAddress localAddress();   // 本地地址
    SocketAddress remoteAddress();  // 遠端地址
    ChannelPromise voidPromise();   // 不關(guān)心結(jié)果的異步Promise?
    ChannelOutboundBuffer outboundBuffer(); // 寫緩沖區(qū)
    void register(EventLoop eventLoop, ChannelPromise promise);
    void bind(SocketAddress localAddress, ChannelPromise promise);
    void connect(SocketAddress remoteAddress, SocketAddress localAddress, 
                              ChannelPromise promise);
    void disconnect(ChannelPromise promise);
    void close(ChannelPromise promise);
    void closeForcibly();
    void deregister(ChannelPromise promise);
    void beginRead();
    void write(Object msg, ChannelPromise promise);
    void flush();

也許你已經(jīng)發(fā)現(xiàn)Unsafe接口和Channel接口中都有register、bind等I/O事件相關(guān)的方法,它們有什么區(qū)別呢?回憶一下EventLoop線程實現(xiàn),當一個selectedKey就緒時,對I/O事件的處理委托給unsafe對象實現(xiàn),代碼類似如下:

    if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
        k.interestOps(k.interestOps() & ~SelectionKey.OP_CONNECT); 
        unsafe.finishConnect(); 
    }
    if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0
                  || readyOps == 0) {
        unsafe.read(); 
    }
    if ((readyOps & SelectionKey.OP_WRITE) != 0) {
        ch.unsafe().forceFlush();
    }

也就是說,Unsafe的子類作為Channel的內(nèi)部類,負責處理底層NIO相關(guān)的I/O事件。Channel則使用責任鏈的方式通過ChannelPipeline將事件提供給用戶自定義處理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內(nèi)容