回顧這幅圖爪幻,目前為止菱皆,我們明白了兩個(gè)Reactor、acceptor以及異步結(jié)果的原理挨稿。在這一章中仇轻,我們將分析圖中的箭頭部分,將各部件連接起來奶甘。進(jìn)行連接的關(guān)鍵部件正是本章的主角Channel篷店,Channel是網(wǎng)絡(luò)Socket進(jìn)行聯(lián)系的紐帶,可以完成諸如讀臭家、寫疲陕、連接、綁定等I/O操作钉赁。
6.1 總述
6.1.1 Channel
JDK中的Channel是通訊的載體蹄殃,而Netty中的Channel在此基礎(chǔ)上進(jìn)行封裝從而賦予了Channel更多的能力,用戶可以使用Channel進(jìn)行以下操作:
- 查詢Channel的狀態(tài)你踩。
- 配置Channel的參數(shù)窃爷。
- 進(jìn)行Channel支持的I/O操作(read,write姓蜂,connect,bind)医吊。
- 獲取對(duì)應(yīng)的ChannelPipeline钱慢,從而可以自定義處理I/O事件或者其他請(qǐng)求。
為了保證在使用Channel或者處理I/O操作時(shí)不出現(xiàn)錯(cuò)誤卿堂,以下幾點(diǎn)需要特別注意:
-
所有的I/O操作都是異步的
由于采用事件驅(qū)動(dòng)的機(jī)制束莫,所以Netty中的所有IO操作都是異步的。這意味著當(dāng)我們調(diào)用一個(gè)IO操作時(shí)草描,方法會(huì)立即返回并不保證操作已經(jīng)完成览绿。由上一章Future的講解中,我們知道穗慕,這些IO操作會(huì)返回一個(gè)ChannelFuture對(duì)象饿敲,我們需要通過添加監(jiān)聽者的方式執(zhí)行操作完成后需執(zhí)行的代碼。 -
Channel是有等級(jí)的
如果一個(gè)Channel由另一個(gè)Channel創(chuàng)建逛绵,那么他們之間形成父子關(guān)系怀各。比如說倔韭,當(dāng)ServerSocketChannel通過accept()方法接受一個(gè)SocketChannel時(shí),那么SocketChannel的父親是ServerSocketChannel瓢对,調(diào)用SocketChannel的parent()方法返回該ServerSocketChannel對(duì)象寿酌。 -
可以使用向下轉(zhuǎn)型獲取子類的特定操作
某些子類Channel會(huì)提供一些所需的特定操作,可以向下轉(zhuǎn)型到這樣的子類硕蛹,從而獲得特定操作醇疼。比如說,對(duì)于UDP的數(shù)據(jù)報(bào)的傳輸法焰,有特定的join()和leave()操作秧荆,我們可以向下轉(zhuǎn)型到DatagramChannel從而使用這些操作。 -
釋放資源
當(dāng)一個(gè)Channel不再使用時(shí)壶栋,須調(diào)用close()或者close(ChannelPromise)方法釋放資源辰如。
6.1.2 Channel配置參數(shù)
(1).通用參數(shù)
CONNECT_TIMEOUT_MILLIS
????????Netty參數(shù),連接超時(shí)毫秒數(shù)贵试,默認(rèn)值30000毫秒即30秒琉兜。
MAX_MESSAGES_PER_READ
????????Netty參數(shù),一次Loop讀取的最大消息數(shù)毙玻,對(duì)于ServerChannel或者NioByteChannel豌蟋,默認(rèn)值為16,其他Channel默認(rèn)值為1桑滩。默認(rèn)值這樣設(shè)置梧疲,是因?yàn)椋篠erverChannel需要接受足夠多的連接,保證大吞吐量运准,NioByteChannel可以減少不必要的系統(tǒng)調(diào)用select幌氮。
WRITE_SPIN_COUNT
????????Netty參數(shù),一個(gè)Loop寫操作執(zhí)行的最大次數(shù)胁澳,默認(rèn)值為16该互。也就是說,對(duì)于大數(shù)據(jù)量的寫操作至多進(jìn)行16次韭畸,如果16次仍沒有全部寫完數(shù)據(jù)宇智,此時(shí)會(huì)提交一個(gè)新的寫任務(wù)給EventLoop,任務(wù)將在下次調(diào)度繼續(xù)執(zhí)行胰丁。這樣随橘,其他的寫請(qǐng)求才能被響應(yīng)不會(huì)因?yàn)閱蝹€(gè)大數(shù)據(jù)量寫請(qǐng)求而耽誤。
ALLOCATOR
????????Netty參數(shù)锦庸,ByteBuf的分配器机蔗,默認(rèn)值為ByteBufAllocator.DEFAULT,4.0版本為UnpooledByteBufAllocator,4.1版本為PooledByteBufAllocator蜒车。該值也可以使用系統(tǒng)參數(shù)io.netty.allocator.type配置讳嘱,使用字符串值:"unpooled","pooled"酿愧。
RCVBUF_ALLOCATOR
????????Netty參數(shù)沥潭,用于Channel分配接受Buffer的分配器,默認(rèn)值為AdaptiveRecvByteBufAllocator.DEFAULT嬉挡,是一個(gè)自適應(yīng)的接受緩沖區(qū)分配器钝鸽,能根據(jù)接受到的數(shù)據(jù)自動(dòng)調(diào)節(jié)大小∨痈郑可選值為FixedRecvByteBufAllocator拔恰,固定大小的接受緩沖區(qū)分配器。
AUTO_READ
????????Netty參數(shù)基括,自動(dòng)讀取颜懊,默認(rèn)值為True。Netty只在必要的時(shí)候才設(shè)置關(guān)心相應(yīng)的I/O事件风皿。對(duì)于讀操作河爹,需要調(diào)用channel.read()設(shè)置關(guān)心的I/O事件為OP_READ,這樣若有數(shù)據(jù)到達(dá)才能讀取以供用戶處理桐款。該值為True時(shí)咸这,每次讀操作完畢后會(huì)自動(dòng)調(diào)用channel.read(),從而有數(shù)據(jù)到達(dá)便能讀饶д!媳维;否則,需要用戶手動(dòng)調(diào)用channel.read()遏暴。需要注意的是:當(dāng)調(diào)用config.setAutoRead(boolean)方法時(shí)侄刽,如果狀態(tài)由false變?yōu)閠rue,將會(huì)調(diào)用channel.read()方法讀取數(shù)據(jù)朋凉;由true變?yōu)閒alse唠梨,將調(diào)用config.autoReadCleared()方法終止數(shù)據(jù)讀取。
WRITE_BUFFER_HIGH_WATER_MARK
????????Netty參數(shù)侥啤,寫高水位標(biāo)記,默認(rèn)值64KB茬故。如果Netty的寫緩沖區(qū)中的字節(jié)超過該值盖灸,Channel的isWritable()返回False。
WRITE_BUFFER_LOW_WATER_MARK
????????Netty參數(shù)磺芭,寫低水位標(biāo)記赁炎,默認(rèn)值32KB。當(dāng)Netty的寫緩沖區(qū)中的字節(jié)超過高水位之后若下降到低水位,則Channel的isWritable()返回True徙垫。寫高低水位標(biāo)記使用戶可以控制寫入數(shù)據(jù)速度讥裤,從而實(shí)現(xiàn)流量控制。推薦做法是:每次調(diào)用channl.write(msg)方法首先調(diào)用channel.isWritable()判斷是否可寫姻报。
MESSAGE_SIZE_ESTIMATOR
????????Netty參數(shù)己英,消息大小估算器,默認(rèn)為DefaultMessageSizeEstimator.DEFAULT吴旋。估算ByteBuf损肛、ByteBufHolder和FileRegion的大小,其中ByteBuf和ByteBufHolder為實(shí)際大小荣瑟,F(xiàn)ileRegion估算值為0治拿。該值估算的字節(jié)數(shù)在計(jì)算水位時(shí)使用,F(xiàn)ileRegion為0可知FileRegion不影響高低水位笆焰。
SINGLE_EVENTEXECUTOR_PER_GROUP
????????Netty參數(shù)劫谅,單線程執(zhí)行ChannelPipeline中的事件,默認(rèn)值為True嚷掠。該值控制執(zhí)行ChannelPipeline中執(zhí)行ChannelHandler的線程捏检。如果為Trye,整個(gè)pipeline由一個(gè)線程執(zhí)行叠国,這樣不需要進(jìn)行線程切換以及線程同步未檩,是Netty4的推薦做法;如果為False粟焊,ChannelHandler中的處理過程會(huì)由Group中的不同線程執(zhí)行冤狡。
(2).SocketChannel參數(shù)
SO_RCVBUF
????????Socket參數(shù),TCP數(shù)據(jù)接收緩沖區(qū)大小项棠。該緩沖區(qū)即TCP接收滑動(dòng)窗口悲雳,linux操作系統(tǒng)可使用命令:cat /proc/sys/net/ipv4/tcp_rmem
查詢其大小。一般情況下香追,該值可由用戶在任意時(shí)刻設(shè)置合瓢,但當(dāng)設(shè)置值超過64KB時(shí),需要在連接到遠(yuǎn)端之前設(shè)置透典。
SO_SNDBUF
????????Socket參數(shù)晴楔,TCP數(shù)據(jù)發(fā)送緩沖區(qū)大小。該緩沖區(qū)即TCP發(fā)送滑動(dòng)窗口峭咒,linux操作系統(tǒng)可使用命令:cat /proc/sys/net/ipv4/tcp_smem
查詢其大小税弃。
TCP_NODELAY
????????TCP參數(shù),立即發(fā)送數(shù)據(jù)凑队,默認(rèn)值為Ture(Netty默認(rèn)為True而操作系統(tǒng)默認(rèn)為False)则果。該值設(shè)置Nagle算法的啟用,改算法將小的碎片數(shù)據(jù)連接成更大的報(bào)文來最小化所發(fā)送的報(bào)文的數(shù)量,如果需要發(fā)送一些較小的報(bào)文西壮,則需要禁用該算法遗增。Netty默認(rèn)禁用該算法,從而最小化報(bào)文傳輸延時(shí)款青。
SO_KEEPALIVE
????????Socket參數(shù)做修,連接保活可都,默認(rèn)值為False缓待。啟用該功能時(shí),TCP會(huì)主動(dòng)探測(cè)空閑連接的有效性渠牲⌒矗可以將此功能視為TCP的心跳機(jī)制,需要注意的是:默認(rèn)的心跳間隔是7200s即2小時(shí)签杈。Netty默認(rèn)關(guān)閉該功能瘫镇。
SO_REUSEADDR
????????Socket參數(shù),地址復(fù)用答姥,默認(rèn)值False铣除。有四種情況可以使用:(1).當(dāng)有一個(gè)有相同本地地址和端口的socket1處于TIME_WAIT狀態(tài)時(shí),而你希望啟動(dòng)的程序的socket2要占用該地址和端口鹦付,比如重啟服務(wù)且保持先前端口尚粘。(2).有多塊網(wǎng)卡或用IP Alias技術(shù)的機(jī)器在同一端口啟動(dòng)多個(gè)進(jìn)程,但每個(gè)進(jìn)程綁定的本地IP地址不能相同敲长。(3).單個(gè)進(jìn)程綁定相同的端口到多個(gè)socket上郎嫁,但每個(gè)socket綁定的ip地址不同。(4).完全相同的地址和端口的重復(fù)綁定祈噪。但這只用于UDP的多播泽铛,不用于TCP。
SO_LINGER
???????? Netty對(duì)底層Socket參數(shù)的簡(jiǎn)單封裝辑鲤,關(guān)閉Socket的延遲時(shí)間盔腔,默認(rèn)值為-1,表示禁用該功能月褥。-1以及所有<0的數(shù)表示socket.close()方法立即返回弛随,但OS底層會(huì)將發(fā)送緩沖區(qū)全部發(fā)送到對(duì)端。0表示socket.close()方法立即返回宁赤,OS放棄發(fā)送緩沖區(qū)的數(shù)據(jù)直接向?qū)Χ税l(fā)送RST包撵幽,對(duì)端收到復(fù)位錯(cuò)誤。非0整數(shù)值表示調(diào)用socket.close()方法的線程被阻塞直到延遲時(shí)間到或發(fā)送緩沖區(qū)中的數(shù)據(jù)發(fā)送完畢礁击,若超時(shí),則對(duì)端會(huì)收到復(fù)位錯(cuò)誤。
IP_TOS
????????IP參數(shù)哆窿,設(shè)置IP頭部的Type-of-Service字段链烈,用于描述IP包的優(yōu)先級(jí)和QoS選項(xiàng)。
ALLOW_HALF_CLOSURE
????????Netty參數(shù)挚躯,一個(gè)連接的遠(yuǎn)端關(guān)閉時(shí)本地端是否關(guān)閉强衡,默認(rèn)值為False。值為False時(shí)码荔,連接自動(dòng)關(guān)閉漩勤;為True時(shí),觸發(fā)ChannelInboundHandler的userEventTriggered()方法缩搅,事件為ChannelInputShutdownEvent越败。
(3).ServerSocketChannel參數(shù)
SO_RCVBUF
????????已說明,需要注意的是:當(dāng)設(shè)置值超過64KB時(shí)硼瓣,需要在綁定到本地端口前設(shè)置究飞。該值設(shè)置的是由ServerSocketChannel使用accept接受的SocketChannel的接收緩沖區(qū)。
SO_REUSEADDR
????????已說明
SO_BACKLOG
????????Socket參數(shù)堂鲤,服務(wù)端接受連接的隊(duì)列長(zhǎng)度亿傅,如果隊(duì)列已滿,客戶端連接將被拒絕瘟栖。默認(rèn)值葵擎,Windows為200,其他為128半哟。
(4).DatagramChannel參數(shù)
SO_BROADCAST
????????Socket參數(shù)酬滤,設(shè)置廣播模式。
SO_RCVBUF
????????已說明
SO_SNDBUF
????????已說明
SO_REUSEADDR
????????已說明
IP_MULTICAST_LOOP_DISABLED
????????對(duì)應(yīng)IP參數(shù)IP_MULTICAST_LOOP镜沽,設(shè)置本地回環(huán)接口的多播功能敏晤。由于IP_MULTICAST_LOOP返回True表示關(guān)閉,所以Netty加上后綴_DISABLED防止歧義缅茉。
IP_MULTICAST_ADDR
????????對(duì)應(yīng)IP參數(shù)IP_MULTICAST_IF嘴脾,設(shè)置對(duì)應(yīng)地址的網(wǎng)卡為多播模式。
IP_MULTICAST_IF
????????對(duì)應(yīng)IP參數(shù)IP_MULTICAST_IF2蔬墩,同上但支持IPV6译打。
IP_MULTICAST_TTL
????????IP參數(shù),多播數(shù)據(jù)報(bào)的time-to-live即存活跳數(shù)拇颅。
IP_TOS
????????已說明
DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION
????????Netty參數(shù)奏司,DatagramChannel注冊(cè)的EventLoop即表示已激活。
6.1.3 Channel接口
Channel接口中含有大量的方法樟插,我們先對(duì)這些方法分類:
- 狀態(tài)查詢
boolean isOpen(); // 是否開放
boolean isRegistered(); // 是否注冊(cè)到一個(gè)EventLoop
boolean isActive(); // 是否激活
boolean isWritable(); // 是否可寫
open表示Channel的開放狀態(tài)韵洋,True表示Channel可用竿刁,F(xiàn)alse表示Channel已關(guān)閉不再可用。registered表示Channel的注冊(cè)狀態(tài)搪缨,True表示已注冊(cè)到一個(gè)EventLoop食拜,F(xiàn)alse表示沒有注冊(cè)到EventLoop。active表示Channel的激活狀態(tài)副编,對(duì)于ServerSocketChannel负甸,True表示Channel已綁定到端口;對(duì)于SocketChannel痹届,表示Channel可用(open)且已連接到對(duì)端呻待。Writable表示Channel的可寫狀態(tài),當(dāng)Channel的寫緩沖區(qū)outboundBuffer非null且可寫時(shí)返回True队腐。
一個(gè)正常結(jié)束的Channel狀態(tài)轉(zhuǎn)移有以下兩種情況:
REGISTERED->CONNECT/BIND->ACTIVE->CLOSE->INACTIVE->UNREGISTERED
REGISTERED->ACTIVE->CLOSE->INACTIVE->UNREGISTERED
其中第一種是服務(wù)端用于綁定的Channel或者客戶端用于發(fā)起連接的Channel蚕捉,第二種是服務(wù)端接受的SocketChannel。一個(gè)異常關(guān)閉的Channel則不會(huì)服從這樣的狀態(tài)轉(zhuǎn)移香到。
- getter方法
EventLoop eventLoop(); // 注冊(cè)到的EventLoop
Channel parent(); // 父類Channel
ChannelConfig config(); // 配置參數(shù)
ChannelMetadata metadata(); // 元數(shù)據(jù)
SocketAddress localAddress(); // 本地地址
SocketAddress remoteAddress(); // 遠(yuǎn)端地址
Unsafe unsafe(); // Unsafe對(duì)象
ChannelPipeline pipeline(); // 事件管道鱼冀,用于處理IO事件
ByteBufAllocator alloc(); // 字節(jié)緩存分配器
ChannelFuture closeFuture(); // Channel關(guān)閉時(shí)的異步結(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)生響應(yīng)的事件千绪。對(duì)應(yīng)地,有inbound入站事件梗脾,將在ChnanelPipeline一節(jié)中詳述荸型。
6.1.4 Unsafe
Unsafe?直譯中文為不安全炸茧,這曾給我?guī)順O大的困擾瑞妇。如果你是第一次遇到這種接口,一定會(huì)和我感同身受梭冠。一個(gè)Unsafe對(duì)象是不安全的辕狰?這里說的不安全,是相對(duì)于用戶程序員而言的控漠,也就是說蔓倍,用戶程序員使用Netty進(jìn)行編程時(shí)不會(huì)接觸到這個(gè)接口和相關(guān)類。為什么不會(huì)接觸到呢盐捷?因?yàn)轭愃频慕涌诤皖愂荖etty的大量?jī)?nèi)部實(shí)現(xiàn)細(xì)節(jié)偶翅,不會(huì)暴露給用戶程序員。然而我們的目標(biāo)是自頂向下深入分析Netty碉渡,所以有必要深入U(xiǎn)nsafe雷區(qū)聚谁。我們先看Unsafe接口中的方法:
SocketAddress localAddress(); // 本地地址
SocketAddress remoteAddress(); // 遠(yuǎn)端地址
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線程實(shí)現(xiàn),當(dāng)一個(gè)selectedKey就緒時(shí)朵耕,對(duì)I/O事件的處理委托給unsafe對(duì)象實(shí)現(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)部類憔披,負(fù)責(zé)處理底層NIO相關(guān)的I/O事件。Channel則使用責(zé)任鏈的方式通過ChannelPipeline將事件提供給用戶自定義處理爸吮。