回顧這幅圖,目前為止,我們明白了兩個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)錯誤,以下幾點需要特別注意:
-
所有的I/O操作都是異步的
由于采用事件驅(qū)動的機制,所以Netty中的所有IO操作都是異步的。這意味著當我們調(diào)用一個IO操作時,方法會立即返回并不保證操作已經(jīng)完成。由上一章Future的講解中,我們知道,這些IO操作會返回一個ChannelFuture對象,我們需要通過添加監(jiān)聽者的方式執(zhí)行操作完成后需執(zhí)行的代碼。 -
Channel是有等級的
如果一個Channel由另一個Channel創(chuàng)建,那么他們之間形成父子關(guān)系。比如說,當ServerSocketChannel通過accept()方法接受一個SocketChannel時,那么SocketChannel的父親是ServerSocketChannel,調(diào)用SocketChannel的parent()方法返回該ServerSocketChannel對象。 -
可以使用向下轉(zhuǎn)型獲取子類的特定操作
某些子類Channel會提供一些所需的特定操作,可以向下轉(zhuǎn)型到這樣的子類,從而獲得特定操作。比如說,對于UDP的數(shù)據(jù)報的傳輸,有特定的join()和leave()操作,我們可以向下轉(zhuǎn)型到DatagramChannel從而使用這些操作。 -
釋放資源
當一個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接口中含有大量的方法,我們先對這些方法分類:
- 狀態(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)移。
- 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();
- 異步結(jié)果生成
ChannelPromise newPromise();
ChannelFuture newSucceededFuture();
ChannelFuture newFailedFuture(Throwable cause);
- 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將事件提供給用戶自定義處理。