ByteBuf
ByteBuf是一個(gè)節(jié)點(diǎn)容器,里面數(shù)據(jù)包括三部分:
1侄刽、已經(jīng)丟棄的數(shù)據(jù)衷模,這部分?jǐn)?shù)據(jù)是無效的
2鹊汛、可讀字節(jié),這部分?jǐn)?shù)據(jù)是ByteBuf的主體
3阱冶、可寫字節(jié)
這三段數(shù)據(jù)被兩個(gè)指針給劃分出來刁憋,讀指針、寫指針木蹬。
總結(jié)
1至耻、 ByteBuf 本質(zhì)上就是,它引用了一段內(nèi)存镊叁,這段內(nèi)存可以是堆內(nèi)也可以是堆外的尘颓,然后用引用計(jì)數(shù)來控制這段內(nèi)存是否需要被釋放,使用讀寫指針來控制對(duì) ByteBuf 的讀寫晦譬,可以理解為是外觀模式的一種使用
2疤苹、基于讀寫指針和容量、最大可擴(kuò)容容量敛腌,衍生出一系列的讀寫方法卧土,要注意 read/write 與 get/set 的區(qū)別
3、多個(gè) ByteBuf 可以引用同一段內(nèi)存像樊,通過引用計(jì)數(shù)來控制內(nèi)存的釋放尤莺,遵循誰 retain() 誰 release() 的原則
通信協(xié)議編解碼
1、客戶端把一個(gè)Java對(duì)象按照通信協(xié)議轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)包
2生棍、把二進(jìn)制數(shù)據(jù)包發(fā)送到服務(wù)端缝裁,數(shù)據(jù)的傳輸油TCP/IP協(xié)議負(fù)責(zé)
3、服務(wù)端收到二進(jìn)制數(shù)據(jù)包后足绅,按照通信協(xié)議,包裝成Java對(duì)象韩脑。
通信協(xié)議的設(shè)計(jì)
1氢妈、魔數(shù),作用:能夠在第一時(shí)間識(shí)別出這個(gè)數(shù)據(jù)包是不是遵循自定義協(xié)議的段多,也就是無效數(shù)據(jù)包首量,為了安全考慮可以直接關(guān)閉連接以節(jié)省資源。
2、版本號(hào)加缘,
3鸭叙、序列化算法
4、指令
5拣宏、數(shù)據(jù)長度
6沈贝、數(shù)據(jù)
總結(jié)
1、通信協(xié)議是為了服務(wù)端與客戶端交互勋乾,雙方協(xié)商出來的滿足一定規(guī)則的二進(jìn)制格式
2宋下、介紹了一種通用的通信協(xié)議的設(shè)計(jì),包括魔數(shù)辑莫、版本號(hào)学歧、序列化算法標(biāo)識(shí)、指令各吨、數(shù)據(jù)長度枝笨、數(shù)據(jù)幾個(gè)字段,該協(xié)議能夠滿足絕大多數(shù)的通信場(chǎng)景揭蜒。
客戶端登陸
1横浑、客戶端會(huì)構(gòu)建一個(gè)登錄請(qǐng)求對(duì)象,然后通過編碼把請(qǐng)求對(duì)象編碼為 ByteBuf忌锯,寫到服務(wù)端伪嫁。
2、服務(wù)端接受到 ByteBuf 之后偶垮,首先通過解碼把 ByteBuf 解碼為登錄請(qǐng)求響應(yīng)张咳,然后進(jìn)行校驗(yàn)。
3似舵、服務(wù)端校驗(yàn)通過之后脚猾,構(gòu)造一個(gè)登錄響應(yīng)對(duì)象,依然經(jīng)過編碼砚哗,然后再寫回到客戶端龙助。
4、客戶端接收到服務(wù)端的之后蛛芥,解碼 ByteBuf提鸟,拿到登錄響應(yīng)響應(yīng),判斷是否登陸成功仅淑。
客戶端和服務(wù)端收發(fā)信息
判斷客戶端是否登陸成功
1称勋、通過 channel.attr(xxx).set(xx) 的方式,我們可以在登陸成功后涯竟,給Channel綁定一個(gè)登陸成功的標(biāo)志位赡鲜,判斷登陸的時(shí)候取出這個(gè)標(biāo)志位就可以了
pipeline與channelHandler
它們通過責(zé)任鏈設(shè)計(jì)模式來組織代碼邏輯空厌,并且支持邏輯的動(dòng)態(tài)添加和刪除。
ChannelHandler 有兩大子接口:
第一個(gè)子接口是 ChannelInboundHandler银酬,從字面意思也可以猜到嘲更,他是處理讀數(shù)據(jù)的邏輯
第二個(gè)子接口 ChannelOutBoundHandler 是處理寫數(shù)據(jù)的邏輯
這兩個(gè)子接口分別有對(duì)應(yīng)的默認(rèn)實(shí)現(xiàn),ChannelInboundHandlerAdapter揩瞪,和 ChanneloutBoundHandlerAdapter赋朦,它們分別實(shí)現(xiàn)了兩大接口的所有功能,默認(rèn)情況下會(huì)把讀寫事件傳播到下一個(gè) handler壮韭。
總結(jié)
1北发、通過我們前面編寫客戶端服務(wù)端處理邏輯,引出了 pipeline 和 channelHandler 的概念喷屋。
2琳拨、channelHandler 分為 inBound 和 outBound 兩種類型的接口,分別是處理數(shù)據(jù)讀與數(shù)據(jù)寫的邏輯屯曹,可與 tcp 協(xié)議棧聯(lián)系起來狱庇。
3、兩種類型的 handler 均有相應(yīng)的默認(rèn)實(shí)現(xiàn)恶耽,默認(rèn)會(huì)把事件傳遞到下一個(gè)密任,這里的傳遞事件其實(shí)說白了就是把本 handler 的處理結(jié)果傳遞到下一個(gè) handler 繼續(xù)處理。
4偷俭、inBoundHandler 的執(zhí)行順序與我們實(shí)際的添加順序相同浪讳,而 outBoundHandler 則相反。
拆包粘包理論與解決方案
TCP是個(gè)“流”協(xié)議涌萤,所謂流淹遵,就是沒有界限的一串?dāng)?shù)據(jù)。TCP底層并不了解上層業(yè)務(wù)數(shù)據(jù)的具體含義负溪,它會(huì)根據(jù)TCP緩沖區(qū)的實(shí)際情況進(jìn)行包的劃分透揣,所以在業(yè)務(wù)上認(rèn)為,一個(gè)完整的包可能會(huì)被TCP拆分成多個(gè)包進(jìn)行發(fā)送川抡,也有可能把多個(gè)小的包封裝成一個(gè)大的數(shù)據(jù)包發(fā)送辐真,這就是所謂的TCP粘包和拆包的問題。
解決方法
1崖堤、解決思路是在封裝自己的包協(xié)議:包=包內(nèi)容長度(4byte)+包內(nèi)容
2侍咱、對(duì)于粘包問題先讀出包頭即包體長度n,然后再讀取長度為n的包內(nèi)容密幔,這樣數(shù)據(jù)包之間的邊界就清楚了放坏。
3、對(duì)于斷包問題先讀出包頭即包體長度n老玛,由于此次讀取的緩存區(qū)長度小于n,這時(shí)候就需要先緩存這部分的內(nèi)容,等待下次read事件來時(shí)拼接起來形成完整的數(shù)據(jù)包蜡豹。
總結(jié)
1麸粮、拆包器的作用就是根據(jù)我們的自定義協(xié)議,把數(shù)據(jù)拼裝成一個(gè)個(gè)符合我們自定義數(shù)據(jù)包大小的 ByteBuf镜廉,然后送到我們的自定義協(xié)議解碼器去解碼弄诲。
2、Netty 自帶的拆包器包括基于固定長度的拆包器娇唯,基于換行符和自定義分隔符的拆包器齐遵,還有另外一種最重要的基于長度域的拆包器。通常 Netty 自帶的拆包器已完全滿足我們的需求塔插,無需重復(fù)造輪子梗摇。
3、基于 Netty 自帶的拆包器想许,我們可以在拆包之前判斷當(dāng)前連上來的客戶端是否是支持自定義協(xié)議的客戶端伶授,如果不支持,可盡早關(guān)閉流纹,節(jié)省資源糜烹。
Netty 自帶的拆包器
固定長度的拆包器 FixedLengthFrameDecoder
如果你的應(yīng)用層協(xié)議非常簡(jiǎn)單,每個(gè)數(shù)據(jù)包的長度都是固定的漱凝,比如 100疮蹦,那么只需要把這個(gè)拆包器加到 pipeline 中,Netty 會(huì)把一個(gè)個(gè)長度為 100 的數(shù)據(jù)包 (ByteBuf) 傳遞到下一個(gè) channelHandler茸炒。行拆包器 LineBasedFrameDecoder
從字面意思來看愕乎,發(fā)送端發(fā)送數(shù)據(jù)包的時(shí)候,每個(gè)數(shù)據(jù)包之間以換行符作為分隔扣典,接收端通過 LineBasedFrameDecoder 將粘過的 ByteBuf 拆分成一個(gè)個(gè)完整的應(yīng)用層數(shù)據(jù)包妆毕。分隔符拆包器 DelimiterBasedFrameDecoder
DelimiterBasedFrameDecoder 是行拆包器的通用版本,只不過我們可以自定義分隔符贮尖。基于長度域拆包器 LengthFieldBasedFrameDecoder
最后一種拆包器是最通用的一種拆包器笛粘,只要你的自定義協(xié)議中包含長度域字段,均可以使用這個(gè)拆包器來實(shí)現(xiàn)應(yīng)用層拆包湿硝。由于上面三種拆包器比較簡(jiǎn)單薪前,讀者可以自行寫出 demo,接下來关斜,我們就結(jié)合我們小冊(cè)的自定義協(xié)議示括,來學(xué)習(xí)一下如何使用基于長度域的拆包器來拆解我們的數(shù)據(jù)包。
channelHandler 生命周期
ChannelHandler 回調(diào)方法的執(zhí)行順序?yàn)?/p>
handlerAdded() -> channelRegistered() -> channelActive() -> channelRead() -> channelReadComplete()
我們來逐個(gè)解釋一下每個(gè)回調(diào)方法的含義
1痢畜、handlerAdded() :指的是當(dāng)檢測(cè)到新連接之后垛膝,調(diào)用 ch.pipeline().addLast(new LifeCyCleTestHandler()); 之后的回調(diào)鳍侣,表示在當(dāng)前的 channel 中,已經(jīng)成功添加了一個(gè) handler 處理器吼拥。
2倚聚、channelRegistered():這個(gè)回調(diào)方法,表示當(dāng)前的 channel 的所有的邏輯處理已經(jīng)和某個(gè) NIO 線程建立了綁定關(guān)系凿可,類似我們?cè)贜etty 是什么惑折?這小節(jié)中 BIO 編程中,accept 到新的連接枯跑,然后創(chuàng)建一個(gè)線程來處理這條連接的讀寫惨驶,只不過 Netty 里面是使用了線程池的方式,只需要從線程池里面去抓一個(gè)線程綁定在這個(gè) channel 上即可敛助,這里的 NIO 線程通常指的是 NioEventLoop,不理解沒關(guān)系粗卜,后面我們還會(huì)講到。
3辜腺、channelActive():當(dāng) channel 的所有的業(yè)務(wù)邏輯鏈準(zhǔn)備完畢(也就是說 channel 的 pipeline 中已經(jīng)添加完所有的 handler)以及綁定好一個(gè) NIO 線程之后休建,這條連接算是真正激活了,接下來就會(huì)回調(diào)到此方法评疗。
4测砂、channelRead():客戶端向服務(wù)端發(fā)來數(shù)據(jù),每次都會(huì)回調(diào)此方法百匆,表示有數(shù)據(jù)可讀砌些。
5、channelReadComplete():服務(wù)端每次讀完一次完整的數(shù)據(jù)之后加匈,回調(diào)該方法存璃,表示數(shù)據(jù)讀取完畢。
單聊
1雕拼、A 要和 B 聊天纵东,首先 A 和 B 需要與服務(wù)器建立連接,然后進(jìn)行一次登錄流程啥寇,服務(wù)端保存用戶標(biāo)識(shí)和 TCP 連接的映射關(guān)系偎球。
2、A 發(fā)消息給 B辑甜,首先需要將帶有 B 標(biāo)識(shí)的消息數(shù)據(jù)包發(fā)送到服務(wù)器衰絮,然后服務(wù)器從消息數(shù)據(jù)包中拿到 B 的標(biāo)識(shí),找到對(duì)應(yīng)的 B 的連接磷醋,將消息發(fā)送給 B猫牡。
總結(jié):
1、我們定義一個(gè)會(huì)話類 Session 用戶維持用戶的登錄信息邓线,用戶登錄的時(shí)候綁定 Session 與 channel淌友,用戶登出或者斷線的時(shí)候解綁 Session 與 channel煌恢。
2、服務(wù)端處理消息的時(shí)候亩进,通過消息接收方的標(biāo)識(shí)症虑,拿到消息接收方的 channel,調(diào)用 writeAndFlush() 將消息發(fā)送給消息接收方归薛。
群聊
1、A匪蝙,B主籍,C 依然會(huì)經(jīng)歷登錄流程,服務(wù)端保存用戶標(biāo)識(shí)對(duì)應(yīng)的 TCP 連接
2逛球、A 發(fā)起群聊的時(shí)候千元,將 A,B颤绕,C 的標(biāo)識(shí)發(fā)送至服務(wù)端幸海,服務(wù)端拿到之后建立一個(gè)群聊 ID,然后把這個(gè) ID 與 A奥务,B物独,C 的標(biāo)識(shí)綁定
3、群聊里面任意一方在群里聊天的時(shí)候氯葬,將群聊 ID 發(fā)送至服務(wù)端挡篓,服務(wù)端拿到群聊 ID 之后,取出對(duì)應(yīng)的用戶標(biāo)識(shí)帚称,遍歷用戶標(biāo)識(shí)對(duì)應(yīng)的 TCP 連接官研,就可以將消息發(fā)送至每一個(gè)群聊成員
總結(jié)
1、通過標(biāo)識(shí)拿到channel
2闯睹、通過 ChannelGroup戏羽,我們可以很方便地對(duì)一組 channel 進(jìn)行批量操作
群聊的成員管理
性能優(yōu)化
心跳與空閑檢測(cè)
連接假死的現(xiàn)象是:在某一端(服務(wù)端或者客戶端)看來,底層的 TCP 連接已經(jīng)斷開了楼吃,但是應(yīng)用程序并沒有捕獲到始花,因此會(huì)認(rèn)為這條連接仍然是存在的,從 TCP 層面來說所刀,只有收到四次握手?jǐn)?shù)據(jù)包或者一個(gè) RST 數(shù)據(jù)包衙荐,連接的狀態(tài)才表示已斷開。
假死導(dǎo)致兩個(gè)問題
1浮创、對(duì)于服務(wù)端忧吟,每條連接都會(huì)耗費(fèi)cpu和內(nèi)存資源,大量假死的連接會(huì)耗光服務(wù)器的資源
2斩披、對(duì)于客戶端溜族,假死會(huì)造成發(fā)送數(shù)據(jù)超時(shí)讹俊,影響用戶體驗(yàn)
通常,連接假死由以下幾個(gè)原因造成的
1煌抒、應(yīng)用程序出現(xiàn)線程堵塞仍劈,無法進(jìn)行數(shù)據(jù)的讀寫。
2寡壮、客戶端或者服務(wù)端網(wǎng)絡(luò)相關(guān)的設(shè)備出現(xiàn)故障贩疙,比如網(wǎng)卡,機(jī)房故障况既。
3这溅、公網(wǎng)丟包。公網(wǎng)環(huán)境相對(duì)內(nèi)網(wǎng)而言棒仍,非常容易出現(xiàn)丟包悲靴,網(wǎng)絡(luò)抖動(dòng)等現(xiàn)象,如果在一段時(shí)間內(nèi)用戶接入的網(wǎng)絡(luò)連續(xù)出現(xiàn)丟包現(xiàn)象莫其,那么對(duì)客戶端來說數(shù)據(jù)一直發(fā)送不出去癞尚,而服務(wù)端也是一直收不到客戶端來的數(shù)據(jù),連接就一直耗著
服務(wù)端空閑檢測(cè)
1乱陡、如果能一直收到客戶端發(fā)來的數(shù)據(jù)浇揩,那么可以說明這條連接還是活的,因此蛋褥,服務(wù)端對(duì)于連接假死的應(yīng)對(duì)策略就是空閑檢測(cè)临燃。
2、簡(jiǎn)化一下烙心,我們的服務(wù)端只需要檢測(cè)一段時(shí)間內(nèi)膜廊,是否收到過客戶端發(fā)來的數(shù)據(jù)即可,Netty 自帶的 IdleStateHandler 就可以實(shí)現(xiàn)這個(gè)功能淫茵。
IdleStateHandler 的構(gòu)造函數(shù)有四個(gè)參數(shù)爪瓜,其中第一個(gè)表示讀空閑時(shí)間,指的是在這段時(shí)間內(nèi)如果沒有數(shù)據(jù)讀到匙瘪,就表示連接假死铆铆;第二個(gè)是寫空閑時(shí)間,指的是 在這段時(shí)間如果沒有寫數(shù)據(jù)丹喻,就表示連接假死薄货;第三個(gè)參數(shù)是讀寫空閑時(shí)間,表示在這段時(shí)間內(nèi)如果沒有產(chǎn)生數(shù)據(jù)讀或者寫碍论,就表示連接假死谅猾。寫空閑和讀寫空閑為0,表示我們不關(guān)心者兩類條件;最后一個(gè)參數(shù)表示時(shí)間單位税娜。在我們的例子中坐搔,表示的是:如果 15 秒內(nèi)沒有讀到數(shù)據(jù),就表示連接假死敬矩。
在一段時(shí)間之內(nèi)沒有讀到客戶端的數(shù)據(jù)概行,是否一定能判斷連接假死呢?并不能為了防止服務(wù)端誤判弧岳,我們還需要在客戶端做點(diǎn)什么凳忙。
客戶端定時(shí)心跳
服務(wù)端在一段時(shí)間內(nèi)沒有收到客戶端的數(shù)據(jù)有兩種情況
1、連接假死
2禽炬、非假死確實(shí)沒數(shù)據(jù)發(fā)
所以我們要排除第二種情況就能保證連接自然就是假死的消略,定期發(fā)送心跳到服務(wù)端
實(shí)現(xiàn)了每隔 5 秒,向服務(wù)端發(fā)送一個(gè)心跳數(shù)據(jù)包瞎抛,這個(gè)時(shí)間段通常要比服務(wù)端的空閑檢測(cè)時(shí)間的一半要短一些,我們這里直接定義為空閑檢測(cè)時(shí)間的三分之一却紧,主要是為了排除公網(wǎng)偶發(fā)的秒級(jí)抖動(dòng)桐臊。
為了排除是否是因?yàn)榉?wù)端在非假死狀態(tài)下確實(shí)沒有發(fā)送數(shù)據(jù),服務(wù)端也要定期發(fā)送心跳給客戶端晓殊。
總結(jié)
1断凶、要處理假死問題首先我們要實(shí)現(xiàn)客戶端與服務(wù)端定期發(fā)送心跳,在這里巫俺,其實(shí)服務(wù)端只需要對(duì)客戶端的定時(shí)心跳包進(jìn)行回復(fù)认烁。
2、客戶端與服務(wù)端如果都需要檢測(cè)假死介汹,那么直接在 pipeline 的最前方插入一個(gè)自定義 IdleStateHandler却嗡,在 channelIdle() 方法里面自定義連接假死之后的邏輯。
3嘹承、通炒凹郏空閑檢測(cè)時(shí)間要比發(fā)送心跳的時(shí)間的兩倍要長一些,這也是為了排除偶發(fā)的公網(wǎng)抖動(dòng)叹卷,防止誤判撼港。
Netty 的零拷貝實(shí)現(xiàn)
Netty 的接收和發(fā)送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接內(nèi)存進(jìn)行 Socket 讀寫骤竹,不需要進(jìn)行字節(jié)緩沖區(qū)的二次拷貝帝牡。堆內(nèi)存多了一次內(nèi)存拷貝,JVM 會(huì)將堆內(nèi)存 Buffer 拷貝一份到直接內(nèi)存中蒙揣,然后才寫入 Socket 中靶溜。ByteBuffer 由 ChannelConfig 分配,而 ChannelConfig 創(chuàng)建 ByteBufAllocator180默認(rèn)使用 Direct BufferCompositeByteBuf 類可以將多個(gè) ByteBuf 合并為一個(gè)邏輯上的 ByteBuf,避免了傳統(tǒng)通過內(nèi)存拷貝的方式將幾個(gè)小 Buffer 合并成一個(gè)大的 Buffer鸣奔。addComponents 方法將 header 與 body 合并為一個(gè)邏輯上的 ByteBuf,這 兩 個(gè) ByteBuf 在 CompositeByteBuf 內(nèi) 部 都 是 單 獨(dú) 存 在 的 ,
CompositeByteBuf 只是邏輯上是一個(gè)整體
通過 FileRegion 包裝的FileChannel.tranferTo方法 實(shí)現(xiàn)文件傳輸, 可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo) Channel墨技,避免了傳統(tǒng)通過循環(huán) write 方式導(dǎo)致的內(nèi)存拷貝問題惩阶。
通過 wrap 方法, 我們可以將 byte[] 數(shù)組、ByteBuf扣汪、ByteBuffer 等包裝成一個(gè) Netty ByteBuf 對(duì)象, 進(jìn)而避免了拷貝操作断楷。
Selector BUG:若 Selector 的輪詢結(jié)果為空,也沒有 wakeup 或新消息處理崭别,則發(fā)生空輪詢冬筒,CPU 使用率 100%,
Netty 的解決辦法:對(duì) Selector 的 select 操作周期進(jìn)行統(tǒng)計(jì)茅主,每完成一次空的select 操作進(jìn)行一次計(jì)數(shù)舞痰,若在某個(gè)周期內(nèi)連續(xù)發(fā)生 N 次空輪詢,則觸發(fā)了 epoll死循環(huán) bug诀姚。重建 Selector响牛,判斷是否是其他線程發(fā)起的重建請(qǐng)求,若不是則將原 SocketChannel 從舊的 Selector 上去除注冊(cè)赫段,重新注冊(cè)到新的 Selector181上呀打,并將原來的 Selector 關(guān)閉。
客戶端(handler 響應(yīng)處理器)
- 創(chuàng)建群
- 群消息
- 心跳定時(shí)器(ChannelHandlerContext -> EventExecutor -> scheduleAtFixedRate )
- 加入群
- 群成員列表
- 登錄
- 退出登錄
- 發(fā)消息
- 退出群
服務(wù)端(handler Request)
- 權(quán)限認(rèn)證
- 創(chuàng)建群
- 群發(fā)消息
- 心跳(ChannelHandlerContext -> EventExecutor -> scheduleAtFixedRate )
- IMHandler(壓縮 handler - 合并平行 handler)
- 加入群
- 群成員列表
- 登錄
- 退出登錄
- 發(fā)消息
- 退出群
公共
- 空閑檢測(cè)(IMIdleStateHandler 如果 15 秒內(nèi)沒有讀到數(shù)據(jù)糯笙,就表示連接假死贬丛,回調(diào) channelIdle,關(guān)閉channel给涕, ctx.channel().close())
- 編解碼
Bootstrap
Bootstrap的使用很像Builder模式豺憔,Bootstrap就是Builder,EventLoopGroup够庙、Channel和Handler等是各種Part恭应。稍有不同的是,準(zhǔn)備好各種Part后首启,并不是直接build出一個(gè)Product來暮屡,而是直接通過connect()方法使用這個(gè)Product
ChannelPipeline
ChannelPipeline實(shí)際上應(yīng)該叫做ChannelHandlerPipeline,可以把ChannelPipeline看成是一個(gè)ChandlerHandler的鏈表毅桃,當(dāng)需要對(duì)Channel進(jìn)行某種處理的時(shí)候褒纲,Pipeline負(fù)責(zé)依次調(diào)用每一個(gè)Handler進(jìn)行處理。每個(gè)Channel都有一個(gè)屬于自己的Pipeline钥飞,調(diào)用Channel#pipeline()方法可以獲得Channel的Pipeline莺掠,調(diào)用Pipeline#channel()方法可以獲得Pipeline的Channel。
事件的傳播
AbstractChannel直接調(diào)用了Pipeline的write()方法读宙,因?yàn)閣rite是個(gè)outbound事件彻秆,所以DefaultChannelPipeline直接找到tail部分的context,調(diào)用其write()方法:
context的write()方法沿著context鏈往前找,直至找到一個(gè)outbound類型的context為止唇兑,然后調(diào)用其invokeWrite()方法:
ByteBuf和設(shè)計(jì)模式
ByteBufAllocator - 抽象工廠模式
在Netty的世界里酒朵,ByteBuf實(shí)例通常應(yīng)該由ByteBufAllocator來創(chuàng)建。CompositeByteBuf - 組合模式
CompositeByteBuf可以讓我們把多個(gè)ByteBuf當(dāng)成一個(gè)大Buf來處理扎附,ByteBufAllocator提供了compositeBuffer()工廠方法來創(chuàng)建CompositeByteBuf蔫耽。CompositeByteBuf的實(shí)現(xiàn)使用了組合模式
- ByteBufInputStream - 適配器模式
ByteBufInputStream使用適配器模式,使我們可以把ByteBuf當(dāng)做Java的InputStream來使用留夜。同理匙铡,ByteBufOutputStream允許我們把ByteBuf當(dāng)做OutputStream來使用。
- ReadOnlyByteBuf - 裝飾器模式
ReadOnlyByteBuf用適配器模式把一個(gè)ByteBuf變?yōu)橹蛔x碍粥,ReadOnlyByteBuf通過調(diào)用Unpooled.unmodifiableBuffer(ByteBuf)方法獲得:
- ByteBuf - 工廠方法模式
前面也提到過了鳖眼,我們很少需要直接通過構(gòu)造函數(shù)來創(chuàng)建ByteBuf實(shí)例,而是通過Allocator來創(chuàng)建嚼摩。從裝飾器模式可以看出另外一種獲得ByteBuf的方式是調(diào)用ByteBuf的工廠方法钦讳,比如:
ByteBuf#duplicate()
ByteBuf#slice()
PooledByteBuf內(nèi)存池原理分析
Arena -》 Chunk -》 Page
Netty的PooledArena是由多個(gè)Chunk組成的大塊內(nèi)存區(qū)域,而每個(gè)chunk則由一個(gè)或多個(gè)page組成枕面,對(duì)內(nèi)存的組織和管理也就集中在如何管理和組織Chunk和Page了
PoolChunk
chunk主要用來組織和管理多個(gè)page的內(nèi)存分配和釋放蜂厅,Chunk的Page被構(gòu)成一顆二叉樹,假設(shè)一個(gè)Chunk由16個(gè)page組成膊畴。
PoolSubpage
對(duì)于小于一個(gè)Page的內(nèi)存,Netty在page中完成分配病游,每個(gè)Page會(huì)被切分成大小相等的多個(gè)儲(chǔ)存塊唇跨,儲(chǔ)存塊的大小由第一次申請(qǐng)的內(nèi)存塊大小決定。
內(nèi)存回收策略
無論chunk還是page衬衬,都是通過狀態(tài)位來標(biāo)識(shí)內(nèi)存是否可用买猖,不同之處chunk通過在二叉樹對(duì)節(jié)點(diǎn)進(jìn)行標(biāo)識(shí)實(shí)現(xiàn),Page是通過維護(hù)塊的使用狀態(tài)來實(shí)現(xiàn)
Reactor模型
1滋尉、Reactor模型是什么?
2、多線程IO的痛點(diǎn)
服務(wù)器用一個(gè)while循環(huán)誉察,不斷監(jiān)聽端口是否有新的套接字連接摘悴,如果有,那么就調(diào)用一個(gè)處理函數(shù)處理
無法并發(fā)碾篡,效率太低虱而,如果當(dāng)前的請(qǐng)求沒有處理完,那么后面的請(qǐng)求只能被阻塞开泽,服務(wù)器的吞吐量太低
缺點(diǎn)在于資源要求太高牡拇,系統(tǒng)中創(chuàng)建線程是需要比較高的系統(tǒng)資源的,如果連接數(shù)太高,系統(tǒng)無法承受惠呼,而且导俘,線程的反復(fù)創(chuàng)建-銷毀也需要代價(jià)。
改進(jìn)方法是:
采用基于事件驅(qū)動(dòng)的設(shè)計(jì)剔蹋,當(dāng)有事件觸發(fā)時(shí)旅薄,才會(huì)調(diào)用處理器進(jìn)行數(shù)據(jù)處理。使用Reactor模式滩租,對(duì)線程的數(shù)量進(jìn)行控制赋秀,一個(gè)線程處理大量的事件。
3律想、單線程Reactor是什么猎莲?
4、單線程模式的缺點(diǎn)
5技即、多線程的Reactor是什么著洼?
6、基于線程池的改進(jìn)
7而叼、Reactor的優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn)
1)響應(yīng)快身笤,不必為單個(gè)同步時(shí)間所阻塞,雖然Reactor本身依然是同步的葵陵;
2)編程相對(duì)簡(jiǎn)單液荸,可以最大程度的避免復(fù)雜的多線程及同步問題,并且避免了多線程/進(jìn)程的切換開銷脱篙;
3)可擴(kuò)展性娇钱,可以方便的通過增加Reactor實(shí)例個(gè)數(shù)來充分利用CPU資源;
4)可復(fù)用性绊困,reactor框架本身與具體事件處理邏輯無關(guān)文搂,具有很高的復(fù)用性;
缺點(diǎn)
1)相比傳統(tǒng)的簡(jiǎn)單模型秤朗,Reactor增加了一定的復(fù)雜性煤蹭,因而有一定的門檻,并且不易于調(diào)試取视。
2)Reactor模式需要底層的Synchronous Event Demultiplexer支持硝皂,比如Java中的Selector支持,操作系統(tǒng)的select系統(tǒng)調(diào)用支持作谭,如果要自己實(shí)現(xiàn)Synchronous Event Demultiplexer可能不會(huì)有那么高效吧彪。
3) Reactor模式在IO讀寫數(shù)據(jù)時(shí)還是在同一個(gè)線程中實(shí)現(xiàn)的,即使使用多個(gè)Reactor機(jī)制的情況下丢早,那些共享一個(gè)Reactor的Channel如果出現(xiàn)一個(gè)長時(shí)間的數(shù)據(jù)讀寫姨裸,會(huì)影響這個(gè)Reactor中其他Channel的相應(yīng)時(shí)間秧倾,比如在大文件傳輸時(shí),IO操作就會(huì)影響其他Client的相應(yīng)時(shí)間傀缩,因而對(duì)這種操作那先,使用傳統(tǒng)的Thread-Per-Connection或許是一個(gè)更好的選擇,或則此時(shí)使用改進(jìn)版的Reactor模式如Proactor模式赡艰。