netty系列之:netty中常用的字串編碼解碼器

flydean 發表於 2022-05-12
Netty

簡介

字串是我們程式中最常用到的訊息格式,也是最簡單的訊息格式,但是正因為字串string太過簡單,不能附加更多的資訊,所以在netty中選擇的是使用byteBuf作為最底層的訊息傳遞載體。

雖然底層使用的ByteBuf,但是對於程式設計師來說,還是希望能夠使用這種最簡單的字串格式,那麼有什麼簡單的方法嗎?

netty中的字串編碼解碼器

為了解決在netty的channel中傳遞字串的問題,netty提供了針對於字串的編碼和解碼器,分別是StringEncoder和StringDecoder。

我們來看下他們是怎麼在程式中使用的,首先是將StringDecoder和StringEncoder加入channelPipeline中:

   ChannelPipeline pipeline = ...;
  
   // Decoders
   pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(80));
   pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
  
   // Encoder
   pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8));

注意,這裡我們在使用StringDecoder之前還呼叫了LineBasedFrameDecoder,先把資料按行進行分割,然後再進行字串的讀取。

那麼有人要問了,decoder加入了LineBasedFrameDecoder預處理,為什麼寫入的時候沒有新增行的分割符呢?

事實上這裡有兩種處理方式,第一種就是在向channel中寫入字串的時候,手動加上行分隔符,如下所示:

   void channelRead(ChannelHandlerContext ctx, String msg) {
       ch.write("Did you say '" + msg + "'?\n");
   }

如果不想每次都在msg後面加上換行符,那麼可以將StringEncoder替換成為LineEncoder,上面的pipeline就變成下面這樣:

   ChannelPipeline pipeline = ...;
  
   // Decoders
   pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(80));
   pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
  
   // Encoder
   pipeline.addLast("lineEncoder", new LineEncoder(LineSeparator.UNIX, CharsetUtil.UTF_8));

這樣,我們在handler中就不需要手動新增換行符了,如下所示:

   void channelRead(ChannelHandlerContext ctx, String msg) {
       ch.write("Did you say '" + msg + "'?");
   }

不同平臺的換行符

在unix和windows平臺傳遞過文字檔案的朋友可能會遇到一個問題,就是windows建立的文字檔案,如果在unix下面開啟的話,會發現每行後面多出了一個特殊字元,這是因為unix和windows平臺定義的換行符是不同的。

在unix平臺通常使用"\n"來換行,而在windows平臺則使用""\r\n"來換行。

java程式因為是跨平臺的,寫出的程式可能執行在unix平臺,也可能執行在windows平臺,所以我們需要有一個辦法來獲取平臺的換行符,netty提供了一個LineSeparator的類來完成這個工作。

LineSeparator中有三個換行符的定義,分別是:

   public static final LineSeparator DEFAULT = new LineSeparator(StringUtil.NEWLINE);

    public static final LineSeparator UNIX = new LineSeparator("\n");

    public static final LineSeparator WINDOWS = new LineSeparator("\r\n");

UNIX和WINDOWS很好理解,他們就是我們剛剛講到的不同的平臺。

那麼什麼是DEFAULT呢?DEFAULT中傳入的NEWLINE,實際上是從系統屬性中獲取到的,如果沒有獲取到,則使用預設的"\n"。

public static final String NEWLINE = SystemPropertyUtil.get("line.separator", "\n");

字串編碼的實現

上面我們講到了和字串編碼解碼相關的類分別是StringEncoder,LineEncoder和StringDecoder,我們來詳細看下這三個類的實現。

首先是StringEncoder,StringEncoder繼承了MessageToMessageEncoder:

public class StringEncoder extends MessageToMessageEncoder<CharSequence> 

泛型中的CharSequence表示StringEncoder要encode的物件是CharSequence,也就是字元序列。

雖然大家常用String這個類,但是不一定大家都知道String其實是CharSequence的子類,所以StringEncoder也可以編碼字串。

StringEncoder的編碼邏輯很簡單,將傳入的字串msg轉換成為CharBuffer,然後呼叫ByteBufUtil的encodeString方法就可以轉換成為ByteBuf,並加入out中去:

    protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
        if (msg.length() == 0) {
            return;
        }
        out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset));
    }

LineEncoder和StringEncoder很類似,它也是繼承自MessageToMessageEncoder:

public class LineEncoder extends MessageToMessageEncoder<CharSequence> 

不同之處在於encoder方法:

    protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
        ByteBuf buffer = ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset, lineSeparator.length);
        buffer.writeBytes(lineSeparator);
        out.add(buffer);
    }

ByteBufUtil的encodeString多了一個lineSeparator.length引數,用來預留lineSeparator的位置,然後在返回的ByteBuf後面加上lineSeparator作為最終的輸出。

StringDecoder是和StringEncoder相反的過程:

public class StringDecoder extends MessageToMessageDecoder<ByteBuf> 

這裡的ByteBuf表示的是要解碼的物件是ByteBuf,我們看下他的解碼方法:

    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        out.add(msg.toString(charset));
    }

直接呼叫msg.toString方法即可將ByteBuf轉換成為字串。

總結

以上就是netty中對字串的編碼解碼器,通過使用這幾個編碼解碼器可以大大簡化我們的工作。

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

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

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