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

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

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

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

? ByteToMessageCodec中維護了一個ByteToMessageDecoder和一個MessageToByteEncoder實例,結合二者的功能,泛型參數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中維護了一個MessageToMessageDecoder和一個 MessageToMessageEncoder實例,結合二者的功能,泛型參數INBOUND_IN和OUTBOUND_IN分別表示需要解碼和編碼的數據類型:一個簡單的總結:ByteToMessageCodec和MessageToMessageCodec中分別實現了字節和對象之間、對象和對象之間的編解碼。