自頂向下深入分析Netty(九)--ByteBuf

Netty架構(gòu)模式

在本節(jié)之前,該系列文章已經(jīng)自頂向下分析了Netty的基本組件:EventLoop认罩,ChannelChannelHandler衫冻,而本節(jié)將分析最后一個(gè)組件:字節(jié)緩沖區(qū)ByteBuf,可認(rèn)為是圖中subReactorreadsend之間的部分眷蜈。

9.1 ByteBuf總述

引入緩沖區(qū)是為了解決速度不匹配的問題沪哺,在網(wǎng)絡(luò)通訊中,CPU處理數(shù)據(jù)的速度大大快于網(wǎng)絡(luò)傳輸數(shù)據(jù)的速度酌儒,所以引入緩沖區(qū)辜妓,將網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)放入緩沖區(qū),累積足夠的數(shù)據(jù)再送給CPU處理。

9.1.1 緩沖區(qū)的使用

ByteBuf是一個(gè)可存儲(chǔ)字節(jié)的緩沖區(qū)籍滴,其中的數(shù)據(jù)可提供給ChannelHandler處理或者將用戶需要寫入網(wǎng)絡(luò)的數(shù)據(jù)存入其中酪夷,待時(shí)機(jī)成熟再實(shí)際寫到網(wǎng)絡(luò)中。由此可知异逐,ByteBuf有讀操作和寫操作捶索,為了便于用戶使用,該緩沖區(qū)維護(hù)了兩個(gè)索引:讀索引和寫索引灰瞻。一個(gè)ByteBuf緩沖區(qū)示例如下:

+-------------------+------------------+------------------+
| discardable bytes |  readable bytes  |  writable bytes  |
|                   |     (CONTENT)    |                  |
+-------------------+------------------+------------------+
|                   |                  |                  |
0      <=      readerIndex   <=   writerIndex    <=    capacity

可知,ByteBuf由三個(gè)片段構(gòu)成:廢棄段辅甥、可讀段和可寫段酝润。其中,可讀段表示緩沖區(qū)實(shí)際存儲(chǔ)的可用數(shù)據(jù)璃弄。當(dāng)用戶使用readXXX()或者skip()方法時(shí)要销,將會(huì)增加讀索引。讀索引之前的數(shù)據(jù)將進(jìn)入廢棄段夏块,表示該數(shù)據(jù)已被使用疏咐。此外,用戶可主動(dòng)使用discardReadBytes()清空廢棄段以便得到跟多的可寫空間脐供,示意圖如下:

清空前:
    +-------------------+------------------+------------------+
    | discardable bytes |  readable bytes  |  writable bytes  |
    +-------------------+------------------+------------------+
    |                   |                  |                  |
    0      <=      readerIndex   <=   writerIndex    <=    capacity
清空后:
    +------------------+--------------------------------------+
    |  readable bytes  |    writable bytes (got more space)   |
    +------------------+--------------------------------------+
    |                  |                                      |
readerIndex (0) <= writerIndex (decreased)       <=       capacity

對(duì)應(yīng)可寫段浑塞,用戶可使用writeXXX()方法向緩沖區(qū)寫入數(shù)據(jù),也將增加寫索引政己。

9.1.2 讀寫索引的非常規(guī)使用

用戶在必要時(shí)可以使用clear()方法清空緩沖區(qū)酌壕,此時(shí)緩沖區(qū)的寫索引和讀索引都將置0,但是并不清除緩沖區(qū)中的實(shí)際數(shù)據(jù)歇由。如果需要循環(huán)使用一個(gè)緩沖區(qū)卵牍,這個(gè)方法很有必要。
此外沦泌,用戶可以使用mark()reset()標(biāo)記并重置讀索引和寫索引糊昙。想象這樣的情形:一個(gè)數(shù)據(jù)需要寫到寫索引為4的位置,之后的另一個(gè)數(shù)據(jù)才寫0-3索引谢谦,此時(shí)可以先mark標(biāo)記0索引释牺,然后byteBuf.writeIndex(4),寫入第一個(gè)數(shù)據(jù)他宛,之后reset重置船侧,寫入第二個(gè)數(shù)據(jù)。用戶可根據(jù)不同的業(yè)務(wù)厅各,合理使用這兩個(gè)方法镜撩。
需要說明的一點(diǎn)是:用戶使用toString(Charset)將緩沖區(qū)的字節(jié)數(shù)據(jù)轉(zhuǎn)為字符串時(shí),并不會(huì)增加讀索引。另外袁梗,toString()只是覆蓋Object的常規(guī)方法宜鸯,僅僅表示緩沖區(qū)的常規(guī)信息,并不會(huì)轉(zhuǎn)化其中的字節(jié)數(shù)據(jù)遮怜。

9.1.3 ByteBuf的底層及派生

容易想到ByteBuf緩沖區(qū)的底層數(shù)據(jù)結(jié)構(gòu)是一個(gè)字節(jié)數(shù)組淋袖。從操作系統(tǒng)的角度理解,緩沖區(qū)的區(qū)別在于字節(jié)數(shù)組是在用戶空間還是內(nèi)核空間锯梁。如果位于用戶空間即碗,對(duì)于JAVA也就是位于堆,此時(shí)可使用JAVA的基本數(shù)據(jù)類型byte[]表示陌凳,用戶可使用array()直接取得該字節(jié)數(shù)組剥懒,使用hasArray()判定該緩沖區(qū)是否是用戶空間緩沖區(qū)。如果位于內(nèi)核空間合敦,JAVA程序?qū)⒉荒苤苯舆M(jìn)行操作初橘,此時(shí)可委托給JDK NIO中的直接緩沖區(qū)DirectByteBuffer由其操作內(nèi)核字節(jié)數(shù)組,用戶可使用nioBuffer()取得直接緩沖區(qū)充岛,使用nioBufferCount()判定底層是否有直接緩沖區(qū)保檐。
用戶可在已有緩沖區(qū)上創(chuàng)建視圖即派生緩沖區(qū),這些視圖維護(hù)各自獨(dú)立的寫索引崔梗、讀索引以及標(biāo)記索引夜只,但他們和原生緩沖區(qū)共享想用的內(nèi)部字節(jié)數(shù)據(jù)。創(chuàng)建視圖即派生緩沖區(qū)的方法有:duplicate()炒俱,slice()以及slice(int,int)盐肃。如果想拷貝緩沖區(qū),也就是說期望維護(hù)特有的字節(jié)數(shù)據(jù)而不是共享字節(jié)數(shù)據(jù)权悟,此時(shí)可使用copy()方法砸王。

9.2 ByteBuf VS ByteBuffer

也許你已經(jīng)發(fā)現(xiàn)了ByteBufByteBuffer在命名上有極大的相似性,JDK的NIO包中既然已經(jīng)有字節(jié)緩沖區(qū)ByteBuffer 的實(shí)現(xiàn)峦阁,為什么Netty還要重復(fù)造輪子呢谦铃?一個(gè)很大的原因是:ByteBuffer對(duì)程序員并不友好。
考慮這樣的需求榔昔,向緩沖區(qū)寫入兩個(gè)字節(jié)0x01和0x02驹闰,然后讀取出這兩個(gè)字節(jié)。如果使用ByteBuffer撒会,代碼是這樣的:

    ByteBuffer buf = ByteBuffer.allocate(4);
    buf.put((byte) 1);
    buf.put((byte) 2);

    buf.flip(); // 從寫模式切換為讀模式
    System.out.println(buf.get());  // 取出0x01
    System.out.println(buf.get());  // 取出0x02

對(duì)于熟悉Netty的ByteBuf的你來說嘹朗,或許只是多了一行buf.flip()用于將緩沖區(qū)從寫模式卻換為讀模式。但事實(shí)并不如此诵肛,注意示例中申請(qǐng)了4個(gè)字節(jié)的空間屹培,此時(shí)理應(yīng)可以繼續(xù)寫入數(shù)據(jù)。不幸的是,如果再次調(diào)用buf.put((byte)3)褪秀,將拋出java.nio.BufferOverflowException蓄诽。而要正確達(dá)到該目的,需要調(diào)用buf.clear()清空整個(gè)緩沖區(qū)或者buf.compact()清除已經(jīng)讀過的數(shù)據(jù)媒吗。
這個(gè)操作雖然有些繁瑣仑氛,但并不是不能忍受,那么繼續(xù)上個(gè)例子闸英,考慮這樣取數(shù)據(jù)的操作:

    buf.flip();
    System.out.println(buf.get(0));
    System.out.println(buf.get(1));
    
    System.out.println(buf.get());
    System.out.println(buf.get());

通過之前的分析锯岖,聰明的你也許已經(jīng)發(fā)現(xiàn)get()操作會(huì)增加讀索引,那么get(index)操作也會(huì)增加讀索引嗎自阱?答案是:并不會(huì)嚎莉,所以這個(gè)代碼示例是正確的,將輸出0 1 0 1的結(jié)果沛豌。什么?get()get(0)居然是兩個(gè)不一樣的操作赃额,前者會(huì)增加讀索引而后者并不會(huì)加派。是的,可以掀桌子了跳芳。此外芍锦,get()的方法名本身就很有迷惑性,很自然的會(huì)認(rèn)為與數(shù)組的get()一致飞盆,但是卻有一個(gè)極大的副作用:增加索引娄琉,所以合理的名字應(yīng)該是:getAndIncreasePosition
又引入了一個(gè)新名詞position吓歇,事實(shí)上ByteBuffer中并沒有讀索引和寫索引的說法,這兩個(gè)索引被統(tǒng)一稱為position女气。在讀寫模式切換時(shí),該值將會(huì)改變炼鞠,正好與事實(shí)上的讀索引與寫索引對(duì)應(yīng)。但愿這樣的說法轰胁,并沒有讓你覺得頭暈谒主。
如果我們使用Netty的ByteBuf赃阀,感覺世界清靜了很多:

    ByteBuf buf2 = Unpooled.buffer(4);
    buf2.writeByte(1);
    buf2.writeByte(2);

    System.out.println(buf2.readByte());
    System.out.println(buf2.readByte());
    buf2.writeByte(3);
    buf2.writeByte(4);

當(dāng)然,如果不幸分配到了噩夢(mèng)模式,必須使用ByteBuffer姿现,那么謹(jǐn)記這四個(gè)步驟:

  1. 寫入數(shù)據(jù)到ByteBuffer
  2. 調(diào)用flip()方法
  3. ByteBuffer中讀取數(shù)據(jù)
  4. 調(diào)用clear()方法或者compact()方法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肠仪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子备典,更是在濱河造成了極大的恐慌,老刑警劉巖提佣,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異潮针,居然都是意外死亡倚喂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門端圈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人矗晃,你說我怎么就攤上這事宴倍。” “怎么了鸵贬?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拯辙。 經(jīng)常有香客問我颜价,道長(zhǎng),這世上最難降的妖魔是什么周伦? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮及志,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘速侈。我一直安慰自己,他們只是感情好冶共,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布每界。 她就那樣靜靜地躺著,像睡著了一般眨层。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上馒闷,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天叁征,我揣著相機(jī)與錄音,去河邊找鬼航揉。 笑死金刁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尤蛮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼醇锚,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼坯临!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赶促,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤挟炬,失蹤者是張志新(化名)和其女友劉穎嗦哆,沒想到半個(gè)月后婿滓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體老速,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡橘券,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年秕铛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鬓梅。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谨湘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出紧阔,到底是詐尸還是另有隱情,我是刑警寧澤擅耽,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布乖仇,位于F島的核電站,受9級(jí)特大地震影響乃沙,放射性物質(zhì)發(fā)生泄漏起趾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一训裆、第九天 我趴在偏房一處隱蔽的房頂上張望蜀铲。 院中可真熱鬧边琉,春花似錦蝙茶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忧额。三九已至,卻和暖如春睦番,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背巩检。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工示启, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人夫嗓。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓舍咖,卻偏偏與公主長(zhǎng)得像矩父,于是被迫代替她去往敵國(guó)和親排霉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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