Netty 編碼和解碼
1. 前言
本節(jié)內(nèi)容,主要是講解 Netty 的編碼和解碼,前面我們講解了 ByteBuf,Netty 是面向 ByteBuf 來(lái)編程的,發(fā)送的內(nèi)容會(huì)被編碼成 ByteBuf,從 Channel 接受的數(shù)據(jù)流則被封裝成了 ByteBuf,需要把它解碼成我們所熟悉的格式。
2. 編碼和解碼的作用
首先,我們先通過(guò)一個(gè)實(shí)例來(lái)進(jìn)行說(shuō)明。
實(shí)例:
ch.pipeline().addLast(new CodecHandler());
public class CodecHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("接受:"+msg.toString());
}
}
客戶(hù)端發(fā)送數(shù)據(jù):
執(zhí)行結(jié)果:
接受:PooledUnsafeDirectByteBuf(ridx: 0, widx: 5, cap: 1024)
通過(guò)以上測(cè)試,發(fā)現(xiàn)客戶(hù)端往服務(wù)端發(fā)送普通的字符串,服務(wù)端接受的時(shí)候并不是正常字符串,而是把 ByteBuf 類(lèi)型打印出來(lái)。
主要原因是,Netty 的數(shù)據(jù)類(lèi)型是 ByteBuf,無(wú)法直接強(qiáng)轉(zhuǎn),需要通過(guò)解碼的方式去轉(zhuǎn)換才能得到正常的數(shù)據(jù),編碼也是同樣道理。
因此,本節(jié)學(xué)編碼和解碼的知識(shí)可以了解 Netty 如何去接受和發(fā)送參數(shù)。
3. 解碼示例
實(shí)例:
public class ServerLoginHandler extends ChannelInboundHandlerAdapter {
//1.讀取客戶(hù)端發(fā)送過(guò)來(lái)的數(shù)據(jù)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//1.轉(zhuǎn)換ByteBuf
ByteBuf buffer=(ByteBuf)msg;
//2.定義一個(gè)byte數(shù)組,長(zhǎng)度是ByteBuf的可讀字節(jié)數(shù)
byte[] bytes=new byte[buffer.readableBytes()];
//3.往自定義的byte[]讀取數(shù)據(jù)
buffer.readBytes(bytes);
//4.字節(jié)流->字符串
String str=new String(bytes);
}
}
通過(guò)以上代碼,我們發(fā)現(xiàn)能正常接收并且打印客戶(hù)端發(fā)送過(guò)來(lái)的字符串?dāng)?shù)據(jù)。但是如果是其它的類(lèi)型數(shù)據(jù)(比如:Map,實(shí)體,List 等)那么還得手工寫(xiě)另外的轉(zhuǎn)換方法,相對(duì)比較麻煩。
4. 編碼解碼流程
4.1 整體流程
無(wú)論是使用 Netty 還是原始的 Socket 編程,基于 TCP 通信的數(shù)據(jù)包格式均為二進(jìn)制,但是我們平時(shí)開(kāi)發(fā)不可能基于二進(jìn)制去開(kāi)發(fā),而是封裝一個(gè)一個(gè)的實(shí)體。這樣的話(huà),我們就需要實(shí)現(xiàn)實(shí)體和二進(jìn)制之間的編碼和解碼了。
- 客戶(hù)端往服務(wù)端發(fā)送消息,手寫(xiě)需要把實(shí)體轉(zhuǎn)換成 byte [],并且把 byte [] 寫(xiě)入到 ByteBuf 容器里面,最終轉(zhuǎn)換二進(jìn)制。其實(shí),整個(gè)過(guò)程就是一個(gè)編碼的過(guò)程;
- 服務(wù)端接受到消息,二進(jìn)制是給機(jī)器去識(shí)別的,人眼無(wú)法快速去識(shí)別它,然而實(shí)體是我們所熟悉并且一看就能看出有哪些屬性,因此需要把二進(jìn)制轉(zhuǎn)換我們所熟悉的實(shí)體,整個(gè)過(guò)程就是一個(gè)解碼的過(guò)程。
4.2 編碼流程
實(shí)例:
//封裝編碼方法
public ByteBuf encode(Object obj) {
// 1. 創(chuàng)建 ByteBuf 對(duì)象
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer();
// 2. 序列化 Java 對(duì)象
byte[] bytes = SerializeUtils.serialize(obj);
// 3. 實(shí)際編碼過(guò)程
byteBuf.writeBytes(bytes);
return byteBuf;
}
//序列化工具類(lèi)
public class SerializeUtils{
//序列化方法
public static byte[] serialize(Object obj){
//省略序列化過(guò)程
return null;
}
}
代碼說(shuō)明:
- 創(chuàng)建一個(gè) ByteBuf(前面章節(jié)詳細(xì)講解過(guò));
- 把內(nèi)容序列化成字節(jié)數(shù)組;
- 把字節(jié)數(shù)組寫(xiě)入到 ByteBuf。
4.3 解碼流程
實(shí)例:
//解碼
public <T> T decode(ByteBuf byteBuf,Class clazz) {
// 數(shù)據(jù)包長(zhǎng)度
int length = byteBuf.readableBytes();
byte[] bytes = new byte[length];
byteBuf.readBytes(bytes);
return SerializeUtils.desrialize(bytes,clazz);
}
//序列化工具類(lèi)
public class SerializeUtils{
//序列化方法
public static <T> T desrialize(byte[] bytes,Class clazz){
//省略反序列化過(guò)程
return null;
}
}
代碼說(shuō)明:
- 根據(jù) ByteBuf 獲取可讀的數(shù)據(jù)長(zhǎng)度;
- 根據(jù)數(shù)據(jù)長(zhǎng)度創(chuàng)建相應(yīng)的字節(jié)數(shù)組;
- 把 ByteBuf 里面的內(nèi)容讀取到自定義的字節(jié)數(shù)組里面;
- 通過(guò)反序列化的手段,把字節(jié)數(shù)組反序列化成對(duì)象。
5. 序列化和反序列化
上面講編碼和解碼的時(shí)候,涉及兩個(gè)空方法沒(méi)有實(shí)現(xiàn),分別是 serialize()
序列化和 desrialize()
反序列化,其實(shí)序列化和反序列化技術(shù)選擇很多,常見(jiàn)的解決方案大概如下:
- 通過(guò)對(duì)象流來(lái)手工實(shí)現(xiàn)序列化,但是實(shí)體必須實(shí)現(xiàn)
Serializeable
序列化接口,否則無(wú)法被正常序列化和反序列化; - 對(duì)象 -> 轉(zhuǎn)換 json 格式的字符串,Java 里面 String 類(lèi)型字符串可以自動(dòng)轉(zhuǎn)換字節(jié)數(shù)組,常見(jiàn)的開(kāi)源框架分別有 Fastjson、Jackjson 等;
- 對(duì)象 - 轉(zhuǎn)存 xml 格式的字符串,常見(jiàn)框架有 XStream 等;
- 其他技術(shù),如:Hessian 序列化、Kryo 序列化等。
這里就不詳細(xì)展開(kāi)展示序列化和反序列化的說(shuō)明,如果有興趣,可以參考我寫(xiě)的另外一篇文章:
接下來(lái),主要說(shuō)明的是,為了靈活擴(kuò)展,我們最好不要寫(xiě)死某種序列化技術(shù),為了方便后期更改技術(shù)框架,因?yàn)槊糠N序列化技術(shù)的差距比較大,主要體現(xiàn)兩點(diǎn):
- 消耗時(shí)間: 序列化和反序列化的消耗時(shí)間長(zhǎng)度;
- 數(shù)據(jù)長(zhǎng)度: 序列化過(guò)后的字節(jié)數(shù)組長(zhǎng)度,這個(gè)是會(huì)影響網(wǎng)絡(luò)傳輸性能的。
一般情況下,通過(guò)面向接口 + 策略模式的方式去解耦,底層可以靈活的切換序列化技術(shù)。
實(shí)例:
//定義一個(gè)序列化接口
public interface SerializeService<T>{
//序列化方法
public byte[] serialize(T t);
//反序列化方法
public T deserialize(byte[] bytes,Class<T> clazz);
}
//具體序列化實(shí)現(xiàn)列
public class JsonSerializeService<T> implements SerializeService<T>{
//序列化方法
public byte[] serialize(T t){
return null;
}
//反序列化方法
public T deserialize(byte[] bytes,Class<T> clazz){
return null;
}
}
//序列化使用
@Component
public class Test{
@Autowired
private SerializeService serializeService;
public ByteBuf encode(Object obj) {
// 1. 創(chuàng)建 ByteBuf 對(duì)象
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer();
// 2. 序列化 Java 對(duì)象
byte[] bytes = serializeService.serialize(obj);
// 3. 實(shí)際編碼過(guò)程
byteBuf.writeBytes(bytes);
return byteBuf;
}
}
6. 小結(jié)
本節(jié)內(nèi)容大家掌握好以下內(nèi)容:
- 編碼和解碼的概念是什么?為什么需要編碼和解碼?
- Netty 如何去進(jìn)行編碼和解碼,以及大體流程是什么?
- 編碼和解碼需要依賴(lài)序列化和反序列化技術(shù),要了解序列化方面的技術(shù)有哪些。
思考題:能否把我們的編碼和解碼封裝成獨(dú)立的 Handler 呢?那么應(yīng)該如何去封裝呢?