簡介
我們知道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/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!