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

flydean發表於2022-04-25

簡介

我們知道netty中資料傳輸的核心是ByteBuf,ByteBuf提供了多種資料讀寫的方法,包括基本型別和byte陣列的讀寫方法。如果要在netty中傳輸這些資料,那麼需要構建ByteBuf,然後呼叫ByteBuf中對應的方法寫入對應的資料,接著套用netty中標準的模板即可使用。

對於byte陣列來說,如果每次都將其封裝進ByteBuf中,再進行傳輸顯得有些麻煩。於是netty提供了一個基於bytes的核心編碼解碼器。

byte是什麼

那麼byte是什麼呢? byte表示的是一個位元組,也就是8bits。用二進位制表示就是-128-127的範圍。byte是JAVA中的基礎型別。

同時它還有一個wrap型別叫做Byte。

先看下Byte的定義:

public final class Byte extends Number implements Comparable<Byte>

Byte中定義了byte的取值訪問:

    public static final byte   MIN_VALUE = -128;

    public static final byte   MAX_VALUE = 127;

並且還提供了一些基本的工具方法。

因為byte表示的是一個8bits的二進位制,如果不算位運算的話,byte基本上是JAVA中最小的資料儲存單位了。所以JAVA中所有的物件都可以轉換成為byte。

基礎型別的轉換這裡就不多講了。這裡主要看一下字串String和物件Object和byte陣列之間的轉換。

先來看下字串String和byte陣列之間的轉換,也就是String和二進位制之間的轉換。

基本的轉換思路就是將String中的字元進行編碼,然後將編碼過後的字元進行儲存即可。

String類本身提供了一個getBytes方法,可以接受編碼型別,以UTF-8來說,我們來看下轉換方法的呼叫:

    public static byte[] stringToBytes(String str) throws UnsupportedEncodingException {
       return str.getBytes("utf-8");
    }

    public static String bytesToString(byte[] bs) throws UnsupportedEncodingException {
       return new String(bs, "utf-8");
    }

直接呼叫String中的方法即可。

如果是Object物件的話,因為Object本身並沒有提供轉換的方法,所以我們需要藉助於ByteArrayOutputStream的toByteArray方法和ByteArrayInputStream的readObject方法來實現byte陣列和Object之間的轉換,如下所示:

    //物件轉陣列
    public byte[] toByteArray (Object obj) throws IOException {
        try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(obj);
            oos.flush();
            return  bos.toByteArray();
        }
    }

    //陣列轉物件
    public Object toObject (byte[] bytes) throws IOException, ClassNotFoundException {
        try (
            ByteArrayInputStream bis = new ByteArrayInputStream (bytes);
            ObjectInputStream ois = new ObjectInputStream (bis)) {
            return ois.readObject();
        }
    }

netty中的byte陣列的工具類

netty中的核心是ByteBuf,ByteBuf提供了大部分基礎資料型別的read和write方法。當然如果要讀取物件,那麼還是需要將物件轉換成為byte然後再寫入或者從ByteBuf中讀出。

當然,netty中不需要這麼複雜,netty提供了一個Unpooled的工具類用來方便的將byte陣列和ByteBuf進行轉換。

先看下Unpooled方法提供的ByteBuff構建方法:

   ByteBuf heapBuffer    = buffer(128);
   ByteBuf directBuffer  = directBuffer(256);
   ByteBuf wrappedBuffer = wrappedBuffer(new byte[128], new byte[256]);
   ByteBuf copiedBuffer  = copiedBuffer(ByteBuffer.allocate(128));

這是Unpooled提供的幾種ByteBuf的構建方式,其中heapBuffer表示的是在使用者空間構建的buff,directBuffer表示的是直接在系統空間構建的buff。wrappedBuffer是對現有的byte陣列和ByteBuf之上構建的檢視,而copiedBuffer是對byte陣列,byteBuf和字串的拷貝。

這裡我們需要用到wrappedBuffer方法,將byte陣列封裝到ByteBuf中:

    public static ByteBuf wrappedBuffer(byte[] array) {
        if (array.length == 0) {
            return EMPTY_BUFFER;
        }
        return new UnpooledHeapByteBuf(ALLOC, array, array.length);
    }

wrappedBuffer返回了一個UnpooledHeapByteBuf物件,這個物件本身就是一個ByteBuf。這裡將byte陣列作為建構函式傳入UnpooledHeapByteBuf中。

這裡的array是UnpooledHeapByteBuf中的私有變數:

byte[] array;

除了建構函式,UnpooledHeapByteBuf還提供了一個setArray的方法用來設定byte陣列:

    private void setArray(byte[] initialArray) {
        array = initialArray;
        tmpNioBuf = null;
    }

下面是如何從array中構建ByteBuf:

    public ByteBuf setBytes(int index, ByteBuffer src) {
        ensureAccessible();
        src.get(array, index, src.remaining());
        return this;
    }

從ByteBuf中讀取byte陣列,可以呼叫ByteBufUtil的getBytes方法:

    public static byte[] getBytes(ByteBuf buf) {
        return getBytes(buf,  buf.readerIndex(), buf.readableBytes());
    }

netty中byte的編碼器

萬事俱備只欠東風,有了上面netty提供的工具類,我們就可以使用這些工具類構建基於byte的編碼器了。

netty中基於byte的編碼解碼器分別叫做ByteArrayEncoder和ByteArrayDecoder。

先來看下這兩個類是如何使用的,這裡以一個典型的TCP/IP應用為例:

   ChannelPipeline pipeline = ...;
  
   // Decoders
   pipeline.addLast("frameDecoder",
                    new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4));
   pipeline.addLast("bytesDecoder",
                    new ByteArrayDecoder());
  
   // Encoder
   pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
   pipeline.addLast("bytesEncoder", new ByteArrayEncoder());

這裡的LengthFieldBasedFrameDecoder和LengthFieldPrepender是以訊息長度為分割標準的frame分割器。這裡我們主要關注ChannelPipeline中新增的ByteArrayDecoder和ByteArrayEncoder。

新增了byte的編碼和解碼器之後,就可以直接在handler中直接使用byte陣列,如下所示:

   void channelRead(ChannelHandlerContext ctx, byte[] bytes) {
       ...
   }

先來看下ByteArrayEncoder,這是一個編碼器,它的實現很簡單:

public class ByteArrayEncoder extends MessageToMessageEncoder<byte[]> {
    @Override
    protected void encode(ChannelHandlerContext ctx, byte[] msg, List<Object> out) throws Exception {
        out.add(Unpooled.wrappedBuffer(msg));
    }
}

具體就是使用Unpooled.wrappedBuffer方法byte陣列封裝成為ByteBuf,然後將其新增到out list中。

同樣的,我們觀察一下ByteArrayDecoder,這是一個解碼器,實現也比較簡單:

public class ByteArrayDecoder extends MessageToMessageDecoder<ByteBuf> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
         // copy the ByteBuf content to a byte array
        out.add(ByteBufUtil.getBytes(msg));
    }
}

具體的實現就是呼叫ByteBufUtil.getBytes方法,將ByteBuf轉換成為byte陣列,然後新增到list物件中。

總結

如果要在netty中傳輸二進位制資料,netty提供的byte編碼和解碼器已經封裝了繁瑣的細節,大家可以放心使用。

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

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

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

相關文章