簡介
我們知道數據在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方法來説,需要下面幾個參數:
- ByteBuf類型的src,這是我們需要進行編碼的源。
- int類型的off和len,表示的是ByteBuf中要編碼數據的位置。
- boolean類型的breakLines,表示是否添加換行符。
- Base64Dialect類型的dialect,表示選擇的base64編碼類型。
- ByteBufAllocator的allocator,表示返回的ByteBuf的生成方式。
同樣的Decode方法,需要下面的幾個參數:
- ByteBuf類型的src,這是我們需要進行解碼的源。
- int類型的off和len,表示的是ByteBuf中要解碼數據的位置。
- Base64Dialect類型的dialect,表示選擇的base64編碼類型。
- 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/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!