《netty入門與實戰》筆記-05:netty內置的channelHandler

Netty 內置了不少開箱即用的 ChannelHandler。下面,咱們經過學習 Netty 內置的 ChannelHandler 來逐步構建咱們的 pipeline。java

ChannelInboundHandlerAdapter 與 ChannelOutboundHandlerAdapter

首先是 ChannelInboundHandlerAdapter ,這個適配器主要用於實現其接口 ChannelInboundHandler 的全部方法,這樣咱們在編寫本身的 handler 的時候就不須要實現 handler 裏面的每一種方法,而只須要實現咱們所關心的方法,默認狀況下,對於 ChannelInboundHandlerAdapter,咱們比較關心的是他的以下方法promise

ChannelInboundHandlerAdapter.java微信

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    ctx.fireChannelRead(msg);
}

他的做用就是接收上一個 handler 的輸出,這裏的 msg 就是上一個 handler 的輸出。你們也能夠看到,默認狀況下 adapter 會經過 fireChannelRead() 方法直接把上一個 handler 的輸出結果傳遞到下一個 handler框架

ChannelInboundHandlerAdapter 相似的類是 ChannelOutboundHandlerAdapter,它的核心方法以下ide

ChannelOutboundHandlerAdapter.java工具

@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    ctx.write(msg, promise);
}

默認狀況下,這個 adapter 也會把對象傳遞到下一個 outBound 節點,它的傳播順序與 inboundHandler 相反,這裏就再也不對這個類展開了。學習

咱們往 pipeline 添加的第一個 handler 中的 channelRead 方法中,msg 對象其實就是 ByteBuf。服務端在接受到數據以後,應該首先要作的第一步邏輯就是把這個 ByteBuf 進行解碼,而後把解碼後的結果傳遞到下一個 handler,像這樣編碼

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf requestByteBuf = (ByteBuf) msg;
        // 解碼
        Packet packet = PacketCodeC.INSTANCE.decode(requestByteBuf);
        // 解碼後的對象傳遞到下一個 handler 處理
        ctx.fireChannelRead(packet)
}

不過在開始解碼以前,咱們來了解一下另一個特殊的 handlernetty

ByteToMessageDecoder

一般狀況下,不管咱們是在客戶端仍是服務端,當咱們收到數據以後,首先要作的事情就是把二進制數據轉換到咱們的一個 Java 對象,因此 Netty 很貼心地寫了一個父類,來專門作這個事情,下面咱們來看一下,如何使用這個類來實現服務端的解碼code

public class PacketDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) {
        out.add(PacketCodeC.INSTANCE.decode(in));
    }
}

當咱們繼承了 ByteToMessageDecoder 這個類以後,咱們只須要實現一下 decode() 方法,這裏的 in 你們能夠看到,傳遞進來的時候就已是 ByteBuf 類型,因此咱們再也不須要強轉,第三個參數是 List 類型,咱們經過往這個 List 裏面添加解碼後的結果對象,就能夠自動實現結果往下一個 handler 進行傳遞,這樣,咱們就實現瞭解碼的邏輯 handler

另外,值得注意的一點,對於 Netty 裏面的 ByteBuf,咱們使用 4.1.6.Final 版本,默認狀況下用的是堆外內存,在 ByteBuf 這一小節中咱們提到,堆外內存咱們須要自行釋放,在咱們前面小節的解碼的例子中,其實咱們已經漏掉了這個操做,這一點是很是致命的,隨着程序運行愈來愈久,內存泄露的問題就慢慢暴露出來了, 而這裏咱們使用 ByteToMessageDecoderNetty 會自動進行內存的釋放,咱們不用操心太多的內存管理方面的邏輯。

當咱們經過解碼工具把二進制數據轉換到 Java 對象即指令數據包以後,就能夠針對每一種指令數據包編寫邏輯了。

SimpleChannelInboundHandler

回顧一下咱們前面處理 Java 對象的邏輯

if (packet instanceof LoginRequestPacket) {
    // ...
} else if (packet instanceof MessageRequestPacket) {
    // ...
} else if ...

咱們經過 if else 邏輯進行邏輯的處理,當咱們要處理的指令愈來愈多的時候,代碼會顯得愈來愈臃腫,咱們能夠經過給 pipeline 添加多個 handler(ChannelInboundHandlerAdapter的子類) 來解決過多的 if else 問題,以下

XXXHandler.java

if (packet instanceof XXXPacket) {
    // ...處理
} else {
   ctx.fireChannelRead(packet); 
}

這樣一個好處就是,每次添加一個指令處理器,邏輯處理的框架都是一致的.

可是,你們應該也注意到了,這裏咱們編寫指令處理 handler 的時候,依然編寫了一段咱們其實能夠不用關心的 if else 判斷,而後還要手動傳遞沒法處理的對象 (XXXPacket) 至下一個指令處理器,這也是一段重複度極高的代碼,所以,Netty 基於這種考慮抽象出了一個 SimpleChannelInboundHandler 對象,類型判斷和對象傳遞的活都自動幫咱們實現了,而咱們能夠專一於處理咱們所關心的指令便可。

下面,咱們來看一下如何使用 SimpleChannelInboundHandler 來簡化咱們的指令處理邏輯

LoginRequestHandler.java

public class LoginRequestHandler extends SimpleChannelInboundHandler<LoginRequestPacket> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, LoginRequestPacket loginRequestPacket) {
        // 登陸邏輯
    }
}

SimpleChannelInboundHandler 從字面意思也能夠看到,使用它很是簡單,咱們在繼承這個類的時候,給他傳遞一個泛型參數,而後在 channelRead0() 方法裏面,咱們不用再經過 if 邏輯來判斷當前對象是不是本 handler 能夠處理的對象,也不用強轉,不用往下傳遞本 handler 處理不了的對象,這一切都已經交給父類 SimpleChannelInboundHandler 來實現了,咱們只須要專一於咱們要處理的業務邏輯便可。

上面的 LoginRequestHandler 是用來處理登陸的邏輯,同理,咱們能夠很輕鬆地編寫一個消息處理邏輯處理器

MessageRequestHandler.java

public class MessageRequestHandler extends SimpleChannelInboundHandler<MessageRequestPacket> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageRequestPacket messageRequestPacket) {

    }
}

MessageToByteEncoder

在前面幾個小節,咱們已經實現了登陸和消息處理邏輯,處理完請求以後,咱們都會給客戶端一個響應,在寫響應以前,咱們須要把響應對象編碼成 ByteBuf,結合咱們本小節的內容,最後的邏輯框架以下

public class LoginRequestHandler extends SimpleChannelInboundHandler<LoginRequestPacket> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, LoginRequestPacket loginRequestPacket) {
        LoginResponsePacket loginResponsePacket = login(loginRequestPacket);
        ByteBuf responseByteBuf = PacketCodeC.INSTANCE.encode(ctx.alloc(), loginResponsePacket);
        ctx.channel().writeAndFlush(responseByteBuf);
    }
}

public class MessageRequestHandler extends SimpleChannelInboundHandler<MessageRequestPacket> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageRequestPacket messageRequestPacket) {
        MessageResponsePacket messageResponsePacket = receiveMessage(messageRequestPacket);
        ByteBuf responseByteBuf = PacketCodeC.INSTANCE.encode(ctx.alloc(), messageRequestPacket);
        ctx.channel().writeAndFlush(responseByteBuf);
    }
}

咱們注意到,咱們處理每一種指令完成以後的邏輯是相似的,都須要進行編碼,而後調用 writeAndFlush() 將數據寫到客戶端,這個編碼的過程其實也是重複的邏輯,並且在編碼的過程當中,咱們還須要手動去建立一個 ByteBuf,以下過程

PacketCodeC.java

public ByteBuf encode(ByteBufAllocator byteBufAllocator, Packet packet) {
    // 1. 建立 ByteBuf 對象
    ByteBuf byteBuf = byteBufAllocator.ioBuffer();
    // 2. 序列化 java 對象

    // 3. 實際編碼過程

    return byteBuf;
}

而Netty 提供了一個特殊的 channelHandler 來專門處理編碼邏輯,咱們不須要每一次將響應寫到對端的時候調用一次編碼邏輯進行編碼,也不須要自行建立 ByteBuf,這個類叫作 MessageToByteEncoder,從字面意思也能夠看出,它的功能就是將對象轉換到二進制數據。

下面,咱們來看一下,咱們如何來實現編碼邏輯

public class PacketEncoder extends MessageToByteEncoder<Packet> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) {
        PacketCodeC.INSTANCE.encode(out, packet);
    }
}

PacketEncoder 繼承自 MessageToByteEncoder,泛型參數 Packet 表示這個類的做用是實現 Packet 類型對象到二進制的轉換。

這裏咱們只須要實現 encode() 方法,咱們注意到,在這個方法裏面,第二個參數是 Java 對象,而第三個參數是 ByteBuf 對象,咱們在這個方法裏面要作的事情就是把 Java 對象裏面的字段寫到 ByteBuf,咱們再也不須要自行去分配 ByteBuf,所以,你們注意到,PacketCodeCencode() 方法的定義也改了,下面是更改先後的對比

PacketCodeC.java

// 更改前的定義
public ByteBuf encode(ByteBufAllocator byteBufAllocator, Packet packet) {
    // 1. 建立 ByteBuf 對象
    ByteBuf byteBuf = byteBufAllocator.ioBuffer();
    // 2. 序列化 java 對象

    // 3. 實際編碼過程

    return byteBuf;
}
// 更改後的定義
public void encode(ByteBuf byteBuf, Packet packet) {
    // 1. 序列化 java 對象

    // 2. 實際編碼過程
}

咱們能夠看到,PacketCodeC 再也不須要手動建立對象,再也不須要再把建立完的 ByteBuf 進行返回。當咱們向 pipeline 中添加了這個編碼器以後,咱們在指令處理完畢以後就只須要 writeAndFlush java 對象便可,像這樣

public class LoginRequestHandler extends SimpleChannelInboundHandler<LoginRequestPacket> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, LoginRequestPacket loginRequestPacket) {
        ctx.channel().writeAndFlush(login(loginRequestPacket));
    }
}

public class MessageRequestHandler extends SimpleChannelInboundHandler<MessageResponsePacket> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageResponsePacket messageRequestPacket) {
        ctx.channel().writeAndFlush(receiveMessage(messageRequestPacket));
    }
}

經過咱們前面的分析,能夠看到, Netty 爲了讓咱們邏輯更爲清晰簡潔,幫咱們作了不少工做,能直接用 Netty 自帶的 handler 來解決的問題,不要重複造輪子。在接下里的小節,咱們會繼續探討 Netty 還有哪些開箱即用的 handler

總結

本小節,咱們經過學習 netty 內置的 channelHandler 來逐步構建咱們的服務端 pipeline,經過內置的 channelHandler 能夠減小不少重複邏輯。

  1. 基於 ByteToMessageDecoder,咱們能夠實現自定義解碼,而不用關心 ByteBuf 的強轉和 解碼結果的傳遞。
  2. 基於 SimpleChannelInboundHandler,咱們能夠實現每一種指令的處理,再也不須要強轉,再也不有冗長乏味的 if else 邏輯,不須要手動傳遞對象。
  3. 基於 MessageToByteEncoder,咱們能夠實現自定義編碼,而不用關心 ByteBuf 的建立,不用每次向對端寫 Java 對象都進行一次編碼。

以上內容來源於掘金小冊《Netty 入門與實戰:仿寫微信 IM 即時通信系統》,若想得到更多,更詳細的內容,請用微信掃碼訂閱:

相關文章
相關標籤/搜索