1.Netty的業(yè)務(wù)場(chǎng)景
? 平臺(tái)主要需求是和充電樁對(duì)接,并定時(shí)對(duì)設(shè)備進(jìn)行監(jiān)控檢查,需要使用Netty作為通信中間件來(lái)監(jiān)聽端口,充電樁通過(guò)TCP連接向服務(wù)端發(fā)送指令,后臺(tái)主要是通過(guò)netty的ChannelHandler來(lái)實(shí)現(xiàn)對(duì)硬件數(shù)據(jù)的接收和處理。
2. Netty的主要組件
2.1 Channel
? Channel作為Netty網(wǎng)絡(luò)通信的主體,可以看作是通訊的載體,主要有三個(gè)狀態(tài):打開、關(guān)閉、連接。
? Channel主要的IO操作:讀(read)、寫(write)、連接(connect)、綁定(bind),均為異步,也就是說(shuō)在調(diào)用如上方法后,并不保證IO操作完成,但會(huì)在IO操作成功、失敗或取消后,生成相應(yīng)的記錄保存在一個(gè)憑證中并返回。
2.2 ChannelHandler
? 負(fù)責(zé)Channel中的邏輯處理,可針對(duì)性地?cái)r截處理Channel負(fù)責(zé)的IO操作或事件,然后在它的ChannelPipeline中將其遞交給下一個(gè)handler。ChannelHandler中有許多方法需要實(shí)現(xiàn),一般通過(guò)繼承ChannelHandlerAdapter來(lái)實(shí)現(xiàn)。

2.3 ChannelPipeline
? Netty中,ChannelPipeline相當(dāng)于ChannelHandler的容器,它們可用于攔截和處理channel事件,關(guān)系如下圖:

? ChannelPipeline相當(dāng)于ChannelHandler的容器,channel事件消息在ChannelPipeline中傳播流動(dòng),而ChannelHandler可以針對(duì)性地對(duì)事件進(jìn)行攔截處理、傳遞、忽略或者終止。一個(gè)ChannelHandler會(huì)綁定一個(gè)ChannelHandlerContext對(duì)象,ChannelHandler會(huì)通過(guò)與其對(duì)應(yīng)的Context對(duì)象和ChannelPipeline交互,比如向上或向下傳遞events,動(dòng)態(tài)地修改ChannelPipeline,或者通過(guò)ChannelHandlerContext中的AttributeKeys存儲(chǔ)與handler相關(guān)的信息。
3. Netty服務(wù)端的啟動(dòng)
ServerBootstrap serverBootstrap = new ServerBootstrap(); //啟動(dòng)NIO服務(wù)的輔助啟動(dòng)類
serverBootstrap.group(parentGroup, childGroup).channel(NIOServerSocketChannel.class) //啟動(dòng)服務(wù)時(shí), 通過(guò)反射創(chuàng)建一個(gè)NioServerSocketChannel對(duì)象
//服務(wù)器初始化時(shí)執(zhí)行, 屬于AbstracBootstrap的方法
.handler(new LoggingHandler(LogLevel.INFO)) //handler在初始化時(shí)就會(huì)執(zhí)行,可以設(shè)置打印日志級(jí)別
.option(ChannelOption.SO_BACKLOG, 1024) //設(shè)置tcp緩沖區(qū), 可連接隊(duì)列大小
.option(ChannelOption.SO_REUSEADDR, true) //允許重復(fù)使用本地地址和端口
//客戶端連接成功之后執(zhí)行, 屬于ServerBootstrap的方法,繼承自AbstractBootstrap
.childOption(ChannelOption.SO_KEEPALIVE, true) //兩小時(shí)沒(méi)有數(shù)據(jù)通信時(shí), 啟用心跳保活機(jī)制探測(cè)客戶端是否連接有效
.childOption(ChannelOption.SO_REUSEADDR, true)
.childHandler(serverChannelInit); //childHandler在客戶端成功連接后才執(zhí)行,實(shí)例化ChannelInitializer
ChannelFuture cf = serverBootstrap.bind(port).sync(); //綁定端口, 添加異步阻塞等待服務(wù)器啟動(dòng)完成
if (cf.isSuccess() == true) {
logger.info("NettyServer啟動(dòng)成功");
} else {
logger.error("NettyServer啟動(dòng)失敗", cf.cause());
}
cf.channel().closeFuture().sync(); //等待服務(wù)器套接字關(guān)閉
4. Netty中的編解碼
4.1 解碼器
? 解碼(decode)就是根據(jù)約定的協(xié)議格式,對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行解析解碼(decode),這一功能由解碼器(decoder)完成。這部分的主要工作是:確定協(xié)議、編寫協(xié)議對(duì)應(yīng)的解碼器。Netty中有一套編解碼框架,輸入的數(shù)據(jù)由ChannelInboundHandler處理,自定義的解碼器實(shí)際上就是這個(gè)接口的特殊實(shí)現(xiàn)類。
? 對(duì)于解碼器(decoder),Netty主要提供了抽象基類ByteToMessageDecoder和MessageToMessageDecoder。

4.1.1 抽象類ByteToMessageDecoder
? 用于將接收的二進(jìn)制數(shù)據(jù)(Byte)解碼,得到完整有效的請(qǐng)求報(bào)文(Message)。
? 一般ByteToMessageDecoder解碼內(nèi)容后,會(huì)得到一個(gè)ByteBuf實(shí)例,每個(gè)ByteBuf實(shí)例都包含了一個(gè)完整的報(bào)文信息。可以直接把這些ByteBuf實(shí)例交給之后的ChannelInboundHandler處理,或?qū)yteBuf實(shí)例解析封裝到不同的JAVA實(shí)例對(duì)象后,再交給它處理。不管哪一種情況,之后的ChannelInboundHandler在處理時(shí)不需要再考慮粘包、拆包問(wèn)題。
? ByteToMessageDecoder中常見(jiàn)的實(shí)現(xiàn)類:
FixedLengthFrameDecoder:定長(zhǎng)協(xié)議解碼器,可以指定固定字節(jié)數(shù)算一個(gè)完整報(bào)文
LineBasedFrameDecoder:行分隔符解碼器,遇到n或者rn,則認(rèn)為是一個(gè)完整的報(bào)文
DelimiterBasedFrameDecoder:分隔符解碼器,與LineBasedFrameDecoder類似,但可以自己指定分隔符
LengthFieldBasedFrameDecoder:長(zhǎng)度編碼解碼器,將報(bào)文劃分為報(bào)文頭/報(bào)文體,根據(jù)報(bào)文頭中的Length字段確定報(bào)文體的長(zhǎng)度,因此報(bào)文體的長(zhǎng)度是可變的
JsonObjectDecoder:json格式解碼器,當(dāng)檢測(cè)到匹配數(shù)量的"{" 、”}”或”[””]”時(shí),則認(rèn)為是一個(gè)完整的json對(duì)象或json數(shù)組
這些實(shí)現(xiàn)類,都只是將接收到的二進(jìn)制數(shù)據(jù)解碼,轉(zhuǎn)成ByteBuf實(shí)例后直接交給之后的ChannelInboundHandler處理,并沒(méi)有將ByteBuf實(shí)例中的信息封裝到Java對(duì)象中,因?yàn)镹etty并不清楚報(bào)文具體內(nèi)容,以及需要封裝到哪個(gè)Java對(duì)象,所以需要自己手動(dòng)來(lái)解析ByteBuf實(shí)例并封裝。
? 可以自定義一個(gè)解碼類繼承ByteToMessageDecoder抽象類,重寫它的decode方法:
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
? 參數(shù)列表:
ByteBuf in:解碼前的二進(jìn)制數(shù)據(jù)
List out:解碼后的有效報(bào)文列表,由于tcp可能出現(xiàn)的粘包問(wèn)題,入?yún)⒌膇n中可能含有多個(gè)有效報(bào)文,所以需要將解碼后的報(bào)文添加到List中,或者可能出現(xiàn)拆包,那么in中的數(shù)據(jù)就不足以構(gòu)成一個(gè)有效報(bào)文,這時(shí)無(wú)需向List中添加元素。
? 解碼時(shí)需要尤其注意的是,應(yīng)該先判斷是否能構(gòu)成一整個(gè)有效報(bào)文,再調(diào)用ByteBuf的read方法來(lái)讀取數(shù)據(jù),通過(guò)與in.readableBytes比較,來(lái)判斷in中可讀字節(jié)數(shù)是否大于約定的基本數(shù)據(jù)幀長(zhǎng)度,只有在大于等于的情況下,我們才進(jìn)行解碼,即讀取指定長(zhǎng)度的字節(jié),添加到List中。
4.1.2 抽象類MessageToMessageDecoder
? 用于將一個(gè)本身就包含完整報(bào)文信息的對(duì)象轉(zhuǎn)成另一個(gè)Java對(duì)象。
? ByteToMessageDecoder解碼后將包含了報(bào)文信息的ByteBuf實(shí)例交給后面的ChannelInboundHandler處理,此時(shí)可以在ChannelPipeline中再添加一個(gè)MessageToMessageDecoder,將ByteBuf中的信息解析后封裝到Java對(duì)象中,簡(jiǎn)化接下來(lái)的ChannelInboundHandler操作。或者是要將已經(jīng)封裝好的Java對(duì)象轉(zhuǎn)成其他Java對(duì)象,所以會(huì)出現(xiàn)MessageToMessageDecoder之后接著另一個(gè)MessageToMessageDecoder的情況。比如,Tomcat將瀏覽器發(fā)送過(guò)來(lái)的二進(jìn)制數(shù)據(jù)解析為HttpServletRequest對(duì)象后,我們還需要將其中的數(shù)據(jù)提取出來(lái)封裝成自定義的POJO類,即將現(xiàn)有的Java對(duì)象(HttpServletRequest)轉(zhuǎn)換成另一個(gè)Java對(duì)象(POJO類)。
? 繼承MessageToMessageDecoder抽象類,也是要重寫它的decode方法:
protected abstract void decode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
? 參數(shù)列表:
I msg:配置需要進(jìn)行解碼的參數(shù)
List out:經(jīng)過(guò)MessageToMessageDecoder解析后,得到的Java對(duì)象存入列表
? 那么在ChannelPipieline中它們的處理順序如下:
ChannelPipieline ch=...
ch.addLast(new ByteToMessageDecoder());//ByteToMessageDecoder實(shí)現(xiàn)類
ch.addLast(new MessageToMessageDecoder());//MessageToMessageDecoder實(shí)現(xiàn)類
ch.addLast(new MessageToMessageDecoder());
...
? 需要注意的是,即便是指定MessageToMessageDecoder的傳入類型為ByteBuf,也絕對(duì)不可以用它來(lái)代替ByteToMessageDecoder報(bào)文解析的工作,因?yàn)锽yteToMessageDecoder的內(nèi)部設(shè)計(jì)才是針對(duì)接收到的二進(jìn)制數(shù)據(jù)進(jìn)行解碼,所以除了解碼,它其中還有對(duì)尚不完整的報(bào)文進(jìn)行拆包緩存的功能邏輯,這是MessageToMessageDecoder所不具備的。
? 因此,通常會(huì)先用ByteToMessageDecoder解析報(bào)文以及粘拆包處理,得到完整有效的ByteBuf實(shí)例,之后再交由一個(gè)或多個(gè)MessageToMessageDecoder對(duì)ByteBuf實(shí)例中的數(shù)據(jù)進(jìn)行解析并封裝成POJO類。
4.2 編碼器
? 相對(duì)應(yīng)地,在ChannelOutboundHandler接口下,Netty也提供了MessageToByteEncoder和MessageToMessageEncoder兩個(gè)抽象類來(lái)完成編碼。沒(méi)有解碼器的內(nèi)部邏輯復(fù)雜,編碼只要將數(shù)據(jù)轉(zhuǎn)成約定的二進(jìn)制格式發(fā)送即可,而解碼器除了解析數(shù)據(jù),還要處理粘拆包問(wèn)題。
4.3 編碼解碼器Codec
? Codec同時(shí)具備編解碼功能,它同時(shí)實(shí)現(xiàn)了ChannelInboundHandler和ChannelOutboundHandler兩個(gè)接口,因此數(shù)據(jù)的輸入輸出都能處理。
? Netty提供了一個(gè)ChannelDuplexHandler適配器類,編解碼器的抽象基類ByteToMessageCodec和MessageToMessageCodec都繼承了它,整體繼承關(guān)系如下:

? ByteToMessageCodec中維護(hù)了一個(gè)ByteToMessageDecoder和一個(gè)MessageToByteEncoder實(shí)例,結(jié)合二者的功能,泛型參數(shù)I可指定接受的編碼類型:
public abstract class ByteToMessageCodec<I> extends ChannelDuplexHandler {
private final TypeParameterMatcher outboundMsgMatcher;
private final MessageToByteEncoder<I> encoder;
private final ByteToMessageDecoder decoder = new ByteToMessageDecoder(){…}
...
protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
MessageToMessageCodec中維護(hù)了一個(gè)MessageToMessageDecoder和一個(gè) MessageToMessageEncoder實(shí)例,結(jié)合二者的功能,泛型參數(shù)INBOUND_IN和OUTBOUND_IN分別表示需要解碼和編碼的數(shù)據(jù)類型:一個(gè)簡(jiǎn)單的總結(jié):ByteToMessageCodec和MessageToMessageCodec中分別實(shí)現(xiàn)了字節(jié)和對(duì)象之間、對(duì)象和對(duì)象之間的編解碼。