netty系列之:netty中的核心編碼器base64

flydean發表於2022-04-22

簡介

我們知道資料在netty中傳輸是以ByteBuf的形式進行的,可以說ByteBuf是netty的資料傳輸基礎。但是對於現代的應用程式來說,通常我們需要用到其他的資料結構或者型別。

為了方便我們在程式中的編寫,一種方式就是在將資料傳入到netty中的時候由程式設計師自身將資料格式進行轉換,然後再呼叫netty的系統方法。另外一種方式就是定義一些codec,由netty的內在編碼機制將程式中用到的資料格式和ByteBuf進行自動轉換。

很明顯,使用codec的方式更加簡捷,也更加符合程式的開發規則。

為了方便程式的開發,netty本身在內部定義了一些核心的codec外掛,我們在需要的時候直接選用即可。

本文將會講解netty內部實現codec的方式和一個最核心的編碼器base64。

netty codec的實現邏輯

所有的netty codec的目的就是在資料傳輸過程中進行資料型別的轉換,換句話說就是對資料進行處理。我們知道netty中有兩個對資料進行handler的類,分別是ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter,他們分別對應channel中的inbound訊息和outbound訊息進行處理。

所以很自然的,我們的codec邏輯只需要在這兩個地方新增即可。

netty為我們提供了兩個HandlerAdapter類的繼承類,分別是MessageToMessageDecoder和MessageToMessageEncoder:

public abstract class MessageToMessageEncoder<I> extends ChannelOutboundHandlerAdapter 

public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter

從名字就可以看出來這兩個類分別使用來編碼和解碼用的,所以我們的codec只需要分別實現這兩個類即可。

以下是一個StringToIntegerDecoder和IntegerToStringEncoder的例子:

       public class StringToIntegerDecoder extends
               MessageToMessageDecoder<String> {
  
            @Override
           public void decode(ChannelHandlerContext ctx, String message,
                              List<Object> out) throws Exception {
               out.add(message.length());
           }
       }
   
       public class IntegerToStringEncoder extends
               MessageToMessageEncoder<Integer> {
  
            @Override
           public void encode(ChannelHandlerContext ctx, Integer message, List<Object> out)
                   throws Exception {
               out.add(message.toString());
           }
       }

最簡單的實現就是分別重構這兩個類的decode和encode方法。

netty中Base64的實現

我們知道JDK中已經有了Base64實現的工具類叫做java.util.Base64。但是在netty中又使用了一個新的實現類同樣叫做Base64,它的全稱是io.netty.handler.codec.base64.Base64。

這個Base64類中用到了一個Base64Dialect類,也就是netty中Base64支援的Base64編碼方式。Base64Dialect中提供了下面的幾種型別:

STANDARD
URL_SAFE
ORDERED

其中STANDARD對應的是RFC3548也是JDK中的標準Base64,URL_SAFE對應的是RFC3548中的base64url版本,對應的JDK中的getUrlEncode。

最後一個是ORDERED,代表的是RFC1940,這個編碼實現在JDK中是沒有的。

為什麼JDK中已經有了Base64的工具類,netty中還需要自己建立一個新的類呢?

我們可以考慮一下在netty中Base64用到的場景,通常來說我們是在handler中新增自定義編碼,而這些handler主要是針對於資料流進行處理。

JDK中自帶的Base64實現在定長的資料上使用還是沒問題的,但是如果運用於資料流的處理話,效率就會比較低。所以Netty才需要為base64在流資料的情況下重新實現一個Base64類。

netty中的實現方式使用的是Robert Harder's Public Domain Base64 Encoder/Decoder。這裡就不多講這個演算法的實現邏輯了。感興趣的朋友可以自行探索。

Base64提供了將ByteBuf進行base64編碼和解碼的方法,我們選擇引數最長的方法來觀察,如下所示:

    public static ByteBuf encode(
            ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect, ByteBufAllocator allocator)

    public static ByteBuf decode(
            ByteBuf src, int off, int len, Base64Dialect dialect, ByteBufAllocator allocator)

對於encode方法來說,需要下面幾個引數:

  1. ByteBuf型別的src,這是我們需要進行編碼的源。
  2. int型別的off和len,表示的是ByteBuf中要編碼資料的位置。
  3. boolean型別的breakLines,表示是否新增換行符。
  4. Base64Dialect型別的dialect,表示選擇的base64編碼型別。
  5. ByteBufAllocator的allocator,表示返回的ByteBuf的生成方式。

同樣的Decode方法,需要下面的幾個引數:

  1. ByteBuf型別的src,這是我們需要進行解碼的源。
  2. int型別的off和len,表示的是ByteBuf中要解碼資料的位置。
  3. Base64Dialect型別的dialect,表示選擇的base64編碼型別。
  4. ByteBufAllocator的allocator,表示返回的ByteBuf的生成方式。

netty中的base64編碼和解碼器

剛剛我們介紹了netty中提供的新的Base64工具類,這個工具類提供了將ByteBuf中資料進行編碼和解碼的方法。接下來我們看一下netty是如何使用這個工具類實現netty中的base64編碼和解碼器。

netty中提供了對Base64的編碼和解碼器,分別是Base64Encoder和Base64Decoder, 先來看下Base64編碼解碼器的基本使用:

   ChannelPipeline pipeline = ...;
  
   // Decoders
   pipeline.addLast("frameDecoder", new DelimiterBasedFrameDecoder(80, Delimiters.nulDelimiter()));
   pipeline.addLast("base64Decoder", new Base64Decoder());
  
   // Encoder
   pipeline.addLast("base64Encoder", new Base64Encoder());

用起來很簡單,只需要把Base64Decoder和Base64Encoder新增到pipeline中即可。

有時候Base64Decoder需要和DelimiterBasedFrameDecoder一起使用,尤其是在TCP/IP協議中,因為我們需要根據特定的Delimiters來判斷ByteBuf應該被分割為幾個frames。這樣才能保證資料的有效性。

Base64Encoder

首先來看base64的編碼器,Base64Encoder的實現比較簡單,首先來看下Base64Encoder的定義:

public class Base64Encoder extends MessageToMessageEncoder<ByteBuf> 

Base64Encoder繼承自MessageToMessageEncoder,它傳入的泛型ByteBuf,表示是將ByteBuf編碼為ByteBuf,雖然外部的ByteBuf型別沒有變化,但是ByteBuf中的資料已經被編碼成為Base64了。

接下來是Base64Encoder的建構函式:

    public Base64Encoder(boolean breakLines, Base64Dialect dialect) {
        this.dialect = ObjectUtil.checkNotNull(dialect, "dialect");
        this.breakLines = breakLines;
    }

Base64Encoder可以接受兩個引數,分別是是否有換行符的breakLines和base64編碼方式的Base64Dialect。

它的encode方法也很簡單:

    protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        out.add(Base64.encode(msg, msg.readerIndex(), msg.readableBytes(), breakLines, dialect));
    }

直接使用的是我們上面講到的Base64工具類的encode方法,然後把返回值新增到out物件中。

Base64Decoder

Base64Decoder用來將ByteBuf中的base64編碼的內容解碼成為原始內容,先來看下Base64Decoder的定義:

public class Base64Decoder extends MessageToMessageDecoder<ByteBuf> 

Base64Decoder繼承了MessageToMessageDecoder,傳入的泛型是ByteBuf。

先看下Base64Decoder的建構函式:

public Base64Decoder(Base64Dialect dialect) {
        this.dialect = ObjectUtil.checkNotNull(dialect, "dialect");
    }

Base64Decoder的建構函式很簡單,和Base64Encoder相比它只需要一個引數就是Base64Dialect型別的dialect,表示的是選擇的base64解碼的方式。

接下來就是它的解碼方法:

    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        out.add(Base64.decode(msg, msg.readerIndex(), msg.readableBytes(), dialect));
    }

解碼方法也是呼叫Base64工具類的decode方法,然後將其新增到返回的out list中去。

總結

本章介紹了netty中的核心編碼器Base64,它負責將ByteBuf中的訊息編碼為base64格式,同時提供了對應的解碼器,大家可以在需要的時候進行使用。

本文已收錄於 http://www.flydean.com/14-1-netty-codec-base64/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章