《跟閃電俠學(xué)Netty》閱讀筆記 - Netty入門程序解析
Part1引言
上一節(jié)[《跟閃電俠學(xué)Netty》閱讀筆記 - 開篇入門Netty] 中介紹了Netty的入門程序,本節(jié)如標題所言將會一步步分析入門程序的代碼含義。
Part2思維導(dǎo)圖

Part3服務(wù)端最簡化代碼
?public?static?void?main(String[]?args)?{
????????ServerBootstrap?serverBootstrap?=?new?ServerBootstrap();
????????NioEventLoopGroup?boos?=?new?NioEventLoopGroup();
????????NioEventLoopGroup?worker?=?new?NioEventLoopGroup();
????????serverBootstrap
???.group(boos,?worker)
???.channel(NioServerSocketChannel.class)
???.childHandler(new?ChannelInitializer<NioSocketChannel>()?{
????protected?void?initChannel(NioSocketChannel?ch)?{
?????ch.pipeline().addLast(new?StringDecoder());
?????ch.pipeline().addLast(new?SimpleChannelInboundHandler<String>()?{
??????
??????protected?void?channelRead0(ChannelHandlerContext?ctx,?String?msg)?{
???????System.out.println(msg);
??????}
?????});
????}
???})
???.bind(8000);
????}
1兩個NioEventLoopGroup
服務(wù)端一上來先構(gòu)建兩個對象NioEventLoopGroup
,這兩個對象將直接決定Netty啟動之后的工作模式,在這個案例中boos
和JDK的NIO編程一樣負責(zé)進行新連接的“輪詢”,他會定期檢查客戶端是否已經(jīng)準備好可以接入。worker
則負責(zé)處理boss獲取到的連接,當檢查連接有數(shù)據(jù)可以讀寫的時候就進行數(shù)據(jù)處理。
NioEventLoopGroup?boos?=?new?NioEventLoopGroup();
NioEventLoopGroup?worker?=?new?NioEventLoopGroup();
那么應(yīng)該如何理解?其實這兩個Group
對象簡單的看成是線程池即可,和JDBC的線程池沒什么區(qū)別。通過閱讀源碼可以知道,bossGroup
只用了一個線程來處理遠程客戶端的連接,workerGroup
擁有的線程數(shù)默認為2倍的cpu核心數(shù)。
那么這兩個線程池是如何配合的?boss和worker的工作模式和我們平時上班,老板接活員工干活的模式是類似的。由bossGroup
負責(zé)接待,再轉(zhuǎn)交給workerGroup
來處理具體的業(yè)務(wù)。
整體概念上貼合NIO的設(shè)計思路,不過它要做的更好。
2ServerBootstrap
ServerBootstrap?serverBootstrap?=?new?ServerBootstrap();
serverBootstrap.
?.xxx()
?.xxx()
服務(wù)端引導(dǎo)類是ServerBootstrap
,引導(dǎo)器指的是引導(dǎo)開發(fā)者更方便快速的啟動Netty服務(wù)端/客戶端,
這里使用了比較經(jīng)典的建造者設(shè)計模式。
3group設(shè)置
.group(boos,?worker)
group方法綁定boos和work使其各司其職,這個操作可以看作是綁定線程池。
注意gorup方法一旦確定就意味著Netty的線程模型被固定了,中途不允許切換,整個運行過程Netty會按照代碼實現(xiàn)計算的線程數(shù)提供服務(wù)。
下面是group的api注釋:
Set the EventLoopGroup for the parent (acceptor) and the child (client). These EventLoopGroup's are used to handle all the events and IO for ServerChannel and Channel's.
機翻過來就是:為父(acceptor)和子(client)設(shè)置EventLoopGroup
。這些EventLoopGroup
是用來處理ServerChannel
和Channel
的所有事件和IO的。注意這里的 Channel's
是Netty中的概念,初學(xué)的時候可以簡單的類比為BIO編程的Socket套接字。
4channel
.channel(NioServerSocketChannel.class)
設(shè)置底層編程模型或者說底層通信模式,一旦設(shè)置中途不允許更改。所謂的底層編程模型,其實就是JDK的BIO,NIO模型(Netty擯棄了JDK的AIO編程模型),除此之外Netty
還提供了自己編寫的Epoll
模型,當然日常工作中是用最多的還是NIO模型。
5childHandler
childHandler
方法主要作用是初始化和定義處理鏈來處理請求處理的細節(jié)。在案例代碼當中我們添加了Netty提供的字符串解碼handler(StringDecoder)和由Netty實現(xiàn)的SimpleChannelInboundHandler
簡易腳手架,腳手架中自定義的處理邏輯為打印客戶端發(fā)送的請求數(shù)據(jù)。
.childHandler(new?ChannelInitializer<NioSocketChannel>()?{
????protected?void?initChannel(NioSocketChannel?ch)?{
?????ch.pipeline().addLast(new?StringDecoder());
?????ch.pipeline().addLast(new?SimpleChannelInboundHandler<String>()?{
??????
??????protected?void?channelRead0(ChannelHandlerContext?ctx,?String?msg)?{
???????System.out.println(msg);
??????}
?????});
????}
???})
Handler負責(zé)處理一個I/O事件或攔截一個I/O操作,處理完成將其轉(zhuǎn)發(fā)給其ChannelPipeline
中的下一個處理Handler
,以此形成經(jīng)典的處理鏈條。 比如案例里面StringDecoder解碼處理數(shù)據(jù)之后將會交給SimpleChannelInboundHandler
的channelRead0
方法,該方法中將解碼讀取到的數(shù)據(jù)打印到控制臺。
借助pipeline
,我們可以定義連接收到請求后續(xù)的數(shù)據(jù)讀寫細節(jié)和處理邏輯。為了方便理解,這里可以認為NIoSocketChanne
對應(yīng)BIO編程模型的Socket
套接字 ,NioServerSocketChannel
對應(yīng)BIO編程模型的ServerSocket
。
6bind
.bind(8000)
bind
操作是一個異步方法,它會返回ChannelFuture
,服務(wù)端編碼中可以通過添加監(jiān)聽器方式,自定義在Netty服務(wù)端啟動回調(diào)通知之后的下一步處理邏輯,當然也可以完全不關(guān)心它是否啟動繼續(xù)往下執(zhí)行其他業(yè)務(wù)代碼的處理。
Netty的 ChannelFuture
類注釋中有一個簡單直觀的例子介紹ChannelFuture
的使用。
//?GOOD
??Bootstrap?b?=?...;
??//?Configure?the?connect?timeout?option.
??b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,?10000);
??ChannelFuture?f?=?b.connect(...);
??f.awaitUninterruptibly();
?
??//?Now?we?are?sure?the?future?is?completed.
??assert?f.isDone();
?
??if?(f.isCancelled())?{
??????//?Connection?attempt?cancelled?by?user
??}?else?if?(!f.isSuccess())?{
??????f.cause().printStackTrace();
??}?else?{
??????//?Connection?established?successfully
??}
這個過程類似外面員把外賣送到指定地點之后打電話通知我們。
Part4實踐:服務(wù)端啟動失敗自動遞增端口號重新綁定端口
第一個案例是通過服務(wù)端啟動失敗自動遞增端口號重新綁定端口。
需求
服務(wù)端啟動必須要關(guān)心的問題是指定的端口被占用導(dǎo)致啟動失敗的處理,這里的代碼實踐是利用Netty的API完成服務(wù)端端口在檢測到端口被占用的時候自動+1重試綁定直到所有的端口耗盡。
思路
實現(xiàn)代碼如下:
??
public?class?NettyServerStart?{??
??
????public?static?void?main(String[]?args)?{??
????????ServerBootstrap?serverBootstrap?=?new?ServerBootstrap();??
??
????????NioEventLoopGroup?boss?=?new?NioEventLoopGroup();??
????????NioEventLoopGroup?worker?=?new?NioEventLoopGroup();??
????????int?port?=?10022;??
????????serverBootstrap??
????????????????.group(boss,?worker)??
????????????????.channel(NioServerSocketChannel.class)??
????????????????.handler(new?ChannelInitializer()?{??
???????????????????? ??
????????????????????protected?void?initChannel(Channel?ch)?throws?Exception?{??
????????????????????????//?指定服務(wù)端啟動過程的一些邏輯??
????????????????????????System.out.println("服務(wù)端啟動當中");??
????????????????????}??
????????????????})??
??
????????????????//?指定自定義屬性,客戶端可以根據(jù)此屬性進行一些判斷處理??
????????????????//?可以看作給Channel維護一個Map屬性,這里的channel是服務(wù)端??
????????????????//?允許指定一個新創(chuàng)建的通道的初始屬性。如果該值為空,指定鍵的屬性將被刪除。??
????????????????.attr(AttributeKey.newInstance("hello"),?"hello?world")??
??
????????????????//?給每個連接指定自定義屬性,Channel?進行屬性指定等??
????????????????//?用給定的值在每個?子通道?上設(shè)置特定的AttributeKey。如果該值為空,則AttributeKey將被刪除。??
????????????????//?區(qū)別是是否是?子channel,子Channel代表給客戶端的連接設(shè)置??
????????????????.childAttr(AttributeKey.newInstance("childAttr"),?"childAttr")??
??
????????????????//?客戶端的?Channel?設(shè)置TCP?參數(shù)??
????????????????//?so_backlog?臨時存放已完成三次握手的請求隊列的最大長度,如果頻繁連接可以調(diào)大此參數(shù)??
????????????????.option(ChannelOption.SO_BACKLOG,?1024)??
??
????????????????//?給每個連接設(shè)置TCP參數(shù)??
????????????????//?tcp的心跳檢測,true為開啟??
????????????????.childOption(ChannelOption.SO_KEEPALIVE,?true)??
????????????????//?nagle?算法開關(guān),實時性要求高就關(guān)閉??
????????????????.childOption(ChannelOption.TCP_NODELAY,?true)??
??
????????????????.childHandler(new?ChannelInitializer<NioSocketChannel>()?{??
???????????????????? ??
????????????????????protected?void?initChannel(NioSocketChannel?ch)?throws?Exception?{??
????????????????????????ch.pipeline().addLast(new?StringDecoder());??
????????????????????????ch.pipeline().addLast(new?SimpleChannelInboundHandler<String>()?{??
???????????????????????????? ??
????????????????????????????protected?void?channelRead0(ChannelHandlerContext?ctx,?String?msg)?throws?Exception?{??
????????????????????????????????System.err.println(msg);??
????????????????????????????}??
????????????????????????});??
??
????????????????????}??
??
????????????????});??
????????bind(serverBootstrap,?port);??
????}??
??
????/**??
?????*?自動綁定遞增端口??
?????*?@param?serverBootstrap??
?????*?@param?port??
?????*/??
????public?static?void?bind(ServerBootstrap?serverBootstrap,?int?port){??
????????serverBootstrap.bind(port).addListener(future?->?{??
????????????if(future.isSuccess()){??
????????????????System.out.println("端口綁定成功");??
????????????????System.out.println("綁定端口"+?port?+"成功");??
????????????}else{??
????????????????System.out.println("端口綁定失敗");??
????????????????bind(serverBootstrap,?port+1);??
????????????}??
????????});??
????}??
}
Part5服務(wù)端API其他方法
詳細介紹和解釋API個人認為意義不大,這里僅僅對于常用的API進行解釋:
handler()
:代表服務(wù)端啟動過程當中的邏輯,服務(wù)端啟動代碼中基本很少使用。childHandler()
:用于指定每個新連接數(shù)據(jù)的讀寫處理邏輯,類似流水線上安排每一道工序的處理細節(jié)。attr()
:底層實際上就是一個Map,用戶可以為服務(wù)端Channel指定屬性,可以通過自定義屬性實現(xiàn)一些特殊業(yè)務(wù)。(不推薦這樣做,會導(dǎo)致業(yè)務(wù)代碼和Netty高度耦合)childAttr()
:為每一個連接指定屬性,可以使用channel.attr()
取出屬性。option()
:可以為Channel配置TCP參數(shù)。so_backlog:表示臨時存放三次握手請求隊列(syns_queue:半連接隊列)的最大容量,如果連接頻繁處理新連接變慢,適當擴大此參數(shù)。這個參數(shù)的主要作用是預(yù)防“DOS”攻擊占用。
childOption()
:為每個連接設(shè)置TCP參數(shù)。TCP_NODELAY:是否開啟Nagle算法,如果需要減少網(wǎng)絡(luò)交互次數(shù)建議開啟,要求高實時性建議關(guān)閉。
SO_KEEPALIVE:TCP底層心跳機制。
Part6客戶端最簡化代碼
客戶端的啟動代碼如下。
public?static?void?main(String[]?args)?throws?InterruptedException?{??
????Bootstrap?bootstrap?=?new?Bootstrap();??
????NioEventLoopGroup?eventExecutors?=?new?NioEventLoopGroup();??
????//?引導(dǎo)器引導(dǎo)啟動??
????bootstrap.group(eventExecutors)??
????????????.channel(NioSocketChannel.class)??
????????????.handler(new?ChannelInitializer<Channel>()?{??
???????????????? ??
????????????????protected?void?initChannel(Channel?channel)?throws?Exception?{??
????????????????????channel.pipeline().addLast(new?StringEncoder());??
????????????????}??
????????????});??
??
????//?建立通道??
????Channel?channel?=?bootstrap.connect("127.0.0.1",?8000).channel();??
??
????while?(true){??
????????channel.writeAndFlush(new?Date()?+?"?Hello?world");??
????????Thread.sleep(2000);??
????}??
??
}
客戶端代碼最主要的三個關(guān)注點是:線程模型、IO模型、IO業(yè)務(wù)處理邏輯,其他代碼和服務(wù)端的啟動比較類似。這里依舊是從上往下一條條分析代碼。
7Bootstrap
客戶端連接不需要監(jiān)聽端口,為了和服務(wù)端區(qū)分直接被叫做Bootstrap
,代表客戶端的啟動引導(dǎo)器。
Bootstrap?bootstrap?=?new?Bootstrap();??
8NioEventLoopGroup
Netty中客戶端也同樣需要設(shè)置線程模型才能和服務(wù)端正確交互,客戶端的NioEventLoopGroup
同樣可以看作是線程池,負責(zé)和服務(wù)端的數(shù)據(jù)讀寫處理。
NioEventLoopGroup?eventExecutors?=?new?NioEventLoopGroup();??
9group
客戶端 group 線程池的設(shè)置只需要一個即可,因為主要目的是和服務(wù)端建立連接(只需要一個線程即可)。
.group(eventExecutors)
10channel
和服務(wù)端設(shè)置同理,作用是底層編程模型的設(shè)置。官方注釋中推薦使用NIO / EPOLL / KQUEUE
這幾種,使用最多的是NIO
。
.channel(NioSocketChannel.class)
這里比較好奇如果用OIO模型的客戶端連接NIO的服務(wù)端會怎么樣? 于是做了個實驗,把如下代碼改為OioServerSocketChannel
(生產(chǎn)禁止使用,此方式已被Deprecated),啟動服務(wù)端之后啟動客戶端即可觀察效果。
.channel(OioServerSocketChannel.class)
從實驗結(jié)果來看,顯然不允許這么干。
15:24:00.934?[main]?WARN??io.netty.bootstrap.Bootstrap?-?Unknown?channel?option?'SO_KEEPALIVE'?for?channel?'[id:?0xd0aaab57]'
15:24:00.934?[main]?WARN??io.netty.bootstrap.Bootstrap?-?Unknown?channel?option?'TCP_NODELAY'?for?channel?'[id:?0xd0aaab57]'
11handler
上文介紹服務(wù)端的時候提到過 handler()
代表服務(wù)端啟動過程當中的邏輯,在這里自然就表示客戶端啟動過程的邏輯,客戶端的handler()
可以直接看作服務(wù)端引導(dǎo)器當中的childHandler()
。
這里讀者可能會好奇為什么客戶端代碼用childHandler
呢?答案是Netty為了防止使用者誤解Bootstrap
中只有handler
,所以我們可以直接等同于服務(wù)端的childHandler()
。
吐槽:這個child不child的API名稱看的比較蛋疼,不加以區(qū)分有時候確實容易用錯。這里生活化理解服務(wù)端中的childHandler是身上帶了連接,所以在連接成功之后會調(diào)用,沒有child則代表此時沒有任何連接,所以會發(fā)送在初始化的時候調(diào)用。
而客戶端為什么只保留 handler() 呢?個人理解是客戶端最關(guān)注的是連接上服務(wù)端之后所做的處理,增加初始化的時候做處理沒啥意義,并且會導(dǎo)致設(shè)計變復(fù)雜。
handler
內(nèi)部是對于Channel進行初始化并且添加pipline
自定義客戶端的讀寫邏輯。這里同樣添加Netty提供的StringEncoder
默認會是用字符串編碼模式對于發(fā)送的數(shù)據(jù)進行編碼處理。
channel.pipeline().addLast(new?StringEncoder());??
ChannelInitializer
可以直接類比SocketChannel
。
12connect
當配置都準備好之后,客戶端的最后一步是啟動客戶端并且和服務(wù)端進行TCP三次握手建立連接。這里方法會返回Channel
對象,Netty的connect
支持異步連接。
Channel?channel?=?bootstrap.connect("127.0.0.1",?8000).channel();??
再次強調(diào),connect 是一個異步方法,同樣可以通過給返回的channel對象調(diào)用addListner
添加監(jiān)聽器,在Netty的客戶端成功和服務(wù)端建立連接之后會回調(diào)相關(guān)方法告知監(jiān)聽器所有數(shù)據(jù)準備完成,此時可以在監(jiān)聽器中添加回調(diào)之后的處理邏輯。
我們還可以用監(jiān)聽器對于連接失敗的情況做自定義處理邏輯,比如下面例子將會介紹利用監(jiān)聽器實現(xiàn)客戶端連接服務(wù)端失敗之后,定時自動重連服務(wù)端多次直到重連次數(shù)用完的例子。
Part7實踐:客戶端失敗重連
第二個實踐代碼是客戶端在連接服務(wù)端的時候進行失敗重連。失敗重連在網(wǎng)絡(luò)環(huán)境較差的時候十分有效,但是需要注意這里的代碼中多次重試會逐漸增加時間間隔。
客戶端失敗重連的整體代碼如下:
private?static?void?connect(Bootstrap?bootstrap,?String?host,?int?port,?int?retry)?{??
????bootstrap.connect(host,?port).addListener(future?->?{??
????????if?(future.isSuccess())?{??
????????????System.out.println(new?Date()?+?":?連接成功,啟動控制臺線程……");??
????????????Channel?channel?=?((ChannelFuture)?future).channel();??
????????????startConsoleThread(channel);??
????????}?else?if?(retry?==?0)?{??
????????????System.err.println("重試次數(shù)已用完,放棄連接!");??
????????}?else?{??
????????????//?第幾次重連??
????????????int?order?=?(MAX_RETRY?-?retry)?+?1;??
????????????//?本次重連的間隔??
????????????int?delay?=?1?<<?order;??
????????????System.err.println(new?Date()?+?":?連接失敗,第"?+?order?+?"次重連……");??
????????????bootstrap.config().group().schedule(()?->?connect(bootstrap,?host,?port,?retry?-?1),?delay,?TimeUnit??
????????????????????.SECONDS);??
????????}??
????});??
}
private?static?void?startConsoleThread(Channel?channel)?{??
????ConsoleCommandManager?consoleCommandManager?=?new?ConsoleCommandManager();??
????LoginConsoleCommand?loginConsoleCommand?=?new?LoginConsoleCommand();??
????Scanner?scanner?=?new?Scanner(System.in);??
??
????new?Thread(()?->?{??
????????while?(!Thread.interrupted())?{??
????????????if?(!SessionUtil.hasLogin(channel))?{??
????????????????loginConsoleCommand.exec(scanner,?channel);??
????????????}?else?{??
????????????????consoleCommandManager.exec(scanner,?channel);??
????????????}??
????????}??
????}).start();??
}
加入失敗重連代碼之后,客戶端的啟動代碼需要進行略微調(diào)整,在鏈式調(diào)用中不再使用直接connection
,而是傳遞引導(dǎo)類和相關(guān)參數(shù),通過遞歸的方式實現(xiàn)失敗重連的效果:
connect(bootstrap,?"127.0.0.1",?10999,?MAX_RETRY);
Part8客戶端API其他方法和相關(guān)屬性
13attr()
NioChannel
綁定自定義屬性底層實際為Map
NioSocketChannel
存儲參數(shù)使用此方法取出
14三種TCP關(guān)聯(lián)參數(shù)
SO_KEEPALIVE
對應(yīng)源碼定義如下:
public?static?final?ChannelOption<Boolean>?SO_KEEPALIVE?=?valueOf("SO_KEEPALIVE");
主要關(guān)聯(lián)TCP底層心跳機制。SO_KEEPALIVE
用于開啟或者關(guān)閉保活探測,默認情況下是關(guān)閉的。當SO_KEEPALIVE
開啟時,可以保持連接檢測對方主機是否崩潰,避免(服務(wù)器)永遠阻塞于TCP連接的輸入。
TCP_NODELAY
Nagle 算法解釋
這個參數(shù)的含義是:是否開啟Nagle算法。首先需要注意這個參數(shù)和Linux操作系統(tǒng)的默認值不一樣,true 傳輸?shù)絃inux是關(guān)閉調(diào)Nagle算法。
Nagele算法的出現(xiàn)和以前的網(wǎng)絡(luò)帶寬資源有限有關(guān),為了盡可能的利用網(wǎng)絡(luò)帶寬,TCP總是希望盡可能的發(fā)送足夠大的數(shù)據(jù),Nagle算法就是為了盡可能發(fā)送大塊數(shù)據(jù),避免網(wǎng)絡(luò)中充斥著許多小數(shù)據(jù)塊。
為了理解Nagle算法,我們需要了解TCP的緩沖區(qū)通常會設(shè)置 MSS 參數(shù)。
MSS 參數(shù):除去 IP 和 TCP 頭部之后,一個網(wǎng)絡(luò)包所能容納的 TCP 數(shù)據(jù)的最大長度; 最大 1460。MTU:一個網(wǎng)絡(luò)包的最大長度,以太網(wǎng)中一般為 1500 字節(jié);
為什么最大為1460個字節(jié)? 因為TCP傳輸過程中都會要求綁定 TCP 和 IP 的頭部信息,這樣服務(wù)端才能回送ACK確認收到包正確。

也就是說傳輸大數(shù)據(jù)包的時候,數(shù)據(jù)會按照MSS的值進行切割。回到Nagle算法,它的作用就是定義任意時刻只能有一個未被ACK確認的小段(MSS對應(yīng)切割的一個塊)。
這就意味著當有多個未被ACK確認的小段的時候,此時client端會小小的延遲一下等待合并為更大的數(shù)據(jù)包才發(fā)送。
Netty 默認關(guān)閉了這個算法,意味著一有數(shù)據(jù)就理解發(fā)送,滿足低延遲和高并發(fā)的設(shè)計。
Netty源碼關(guān)聯(lián)
TCP_NODELAY 配置選項定義如下:
public?static?final?ChannelOption<Boolean>?TCP_NODELAY?=?valueOf("TCP_NODELAY");
此參數(shù)的配置介紹可以從 SocketChannelConfig 關(guān)聯(lián)的配置中獲取。
/**??
?*?Gets?the?{@link?StandardSocketOptions#TCP_NODELAY}?option.??Please?note?that?the?default?value?of?this?option??
?*?is?{@code?true}?unlike?the?operating?system?default?({@code?false}).?However,?for?some?buggy?platforms,?such?as??
?*?Android,?that?shows?erratic?behavior?with?Nagle's?algorithm?disabled,?the?default?value?remains?to?be?*?{@code?false}.??
?*/
?boolean?isTcpNoDelay();
注釋翻譯如下。
獲取 StandardSocketOptions.TCP_NODELAY 配置。請注意,該選項的默認值為 true,與操作系統(tǒng)的默認值(false)不同。然而,對于一些有問題的平臺,比如Android,在禁用Nagle算法的情況下會出現(xiàn)不穩(wěn)定的行為,默認值仍然為false。
CONNECTION_TIMEOUT
表示連接超時時間,單位為毫秒。
Part9客戶端和服務(wù)端通信
本部分可以參考作者代碼,這里僅僅用筆記歸檔一下大致代碼編寫思路。
https://github.com/lightningMan/flash-netty
15客戶端寫入數(shù)據(jù)到服務(wù)端
handler
方法:指定客戶端通信處理邏輯initChannel
方法:給客戶端添加邏輯處理器pipeline
:邏輯處理鏈添加邏輯處理器覆蓋channelActive()方法
客戶端連接建立成功提示打印
addLast 添加自定義ChannelHandler
邏輯處理器繼承自ChannelHandler
邏輯處理器可以通過繼承適配類
ChannelInboundHandlerAdapter
實現(xiàn)簡化開發(fā)寫數(shù)據(jù)部分ByteBuf (Netty實現(xiàn))
writeAndFlush 刷緩存
byte[] 數(shù)據(jù)填充二進制數(shù)據(jù)
alloc獲得內(nèi)存管理器
16服務(wù)端讀取客戶端數(shù)據(jù)
邏輯處理器繼承適配類
邏輯處理器可以通過繼承適配類
ChannelInboundHandlerAdapter
實現(xiàn)簡化開發(fā)接收數(shù)據(jù)和服務(wù)端讀取數(shù)據(jù)類似
構(gòu)建ByteBuf
writeAndFlush 刷緩存
byte[] 數(shù)據(jù)填充二進制數(shù)據(jù)
alloc獲得內(nèi)存管理器
通過
writeAndFlush
寫出數(shù)據(jù)給客戶端
17服務(wù)端返回數(shù)據(jù)給客戶端
邏輯處理器繼承適配類
邏輯處理器可以通過繼承適配類ChannelInboundHandlerAdapter實現(xiàn)簡化開發(fā)
接收數(shù)據(jù)和服務(wù)端讀取數(shù)據(jù)類似
構(gòu)建ByteBuf
writeAndFlush 刷緩存
byte[] 數(shù)據(jù)填充二進制數(shù)據(jù)
alloc獲得內(nèi)存管理器
通過writeAndFlush寫出數(shù)據(jù)給客戶端
18客戶端讀取服務(wù)端數(shù)據(jù)
和服務(wù)端讀取客戶端數(shù)據(jù)思路類似
關(guān)鍵是需要覆蓋channelRead() 方法
19核心概念
Netty當中,childHanlder 和 handler 對應(yīng)客戶端和服務(wù)端處理邏輯
ByteBuf 數(shù)據(jù)載體,類似隧道兩端的中間小推車。JDK官方實現(xiàn)
java.nio.ByteBuffer
存在各種問題,Netty官方重新實現(xiàn)了io.netty.buffer.ByteBuf
。服務(wù)端讀取對象基本單位為Object,如果需要讀取其他對象類型通常需要強轉(zhuǎn)。
邏輯處理器都可以通過繼承適配器實現(xiàn),客戶端和服務(wù)端覆蓋對應(yīng)方法實現(xiàn)被動接收或者主動推送。
Part10章節(jié)末尾問題
20客戶端API對比服務(wù)端少了什么內(nèi)容?
“group”。
客戶端只有
childHandler
,
21新連接接入時候,如何實現(xiàn)服務(wù)端主動推送消息,然后客戶端進行回復(fù)?
答案是添加監(jiān)聽器,在監(jiān)聽到客戶端連接成功之后直接主動推送自定義信息。
22handler()和childHandler()有什么區(qū)別
初學(xué)者比較容易困擾的問題。handler()
和childHandler()
的主要區(qū)別是:handler()
是發(fā)生在初始化的時候,childHandler()
是發(fā)生在客戶端連接之后。
“知其所以然”的部分放到后續(xù)的源碼分析筆記當中,這里暫時跳過,初次閱讀只需要記住結(jié)論即可。
Part11八股
23BIO的Socket和NIO的SocketChannel 區(qū)別?
本質(zhì)上都是客戶端和服務(wù)端進行網(wǎng)絡(luò)通信的連接的一種抽象,但是使用上有不小的區(qū)別。下面的內(nèi)容摘錄自參考資料:
Socket、SocketChannel區(qū)別: https://blog.csdn.net/A350204530/article/details/78606298 Netty Channel的理解: https://segmentfault.com/q/1010000011974154