如何自定義編解碼器
1. 前言
上一節(jié)我們一節(jié)了解了什么是編碼解碼、序列化和反序列化了,并且留有一道思考題,本節(jié)內(nèi)容主要是深入解析該思考題。
思考題:能否把我們的編碼和解碼封裝成獨立的 Handler 呢?那么應(yīng)該如何去封裝呢?
2. 為什么要封裝獨立 Handler?
即使我們把編碼和解碼封裝成了方法,但是還是需要在 Handler 業(yè)務(wù)邏輯里面進(jìn)行手工調(diào)用,雖然看似不怎么影響,但是業(yè)務(wù) Handler 不夠純粹,應(yīng)該讓 Handler 只是專心的負(fù)責(zé)處理業(yè)務(wù)邏輯就好。
實例:
ch.pipeline().addLast(new MyEncoderHandler());//解碼Handler
ch.pipeline().addLast(new MyDecoderHandler());//編碼Handler
ch.pipeline().addLast(new MyBusiHandler());//業(yè)務(wù)Handler
public class MyBusiHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//1.接受參數(shù),可以直接強(qiáng)轉(zhuǎn)
UserReq userReq=(UserReq)msg;
//2.相應(yīng)數(shù)據(jù),直接寫對象
UserRes res=new UserRes();
res.setCode(0);
res.setMsg("接受成功");
ctx.writeAndFlush(res);
}
}
通過以上的代碼,我們把編碼和解碼封裝成兩個獨立的 Handler,并且加入到 ChannelPipeline 里面進(jìn)行管理。在我們的業(yè)務(wù) Handler 里面就可以直接操作實體數(shù)據(jù),無需手工轉(zhuǎn)換成字節(jié)數(shù)組了。
思考:那么如何進(jìn)行封裝 Handler 呢?
3. StringDecoder 和 StringEncoder
3.1 簡單使用
StringDecoder 和 StringEncoder 是 Netty 為我們提供的專門針對普通字符串的解碼和編碼器,使用起來非常的簡單。
客戶端直接發(fā)送字符串。
實例:
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new ClientTestHandler());
public class ClientTestHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//客戶端直接寫字符串,沒有任何的數(shù)據(jù)加工
ctx.channel().writeAndFlush("hello world");
}
}
服務(wù)端直接強(qiáng)轉(zhuǎn)字符串。
實例:
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new ServerTestHandler());
public class ServerTestHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//直接把msg轉(zhuǎn)換成String類型
String str=msg.toString();
System.out.println("str="+str);
}
}
總結(jié),這種模式開發(fā)起來實在太方便了,無需做數(shù)據(jù)的加工,我們還是按照我們熟悉的方式去寫代碼,非常的方便。
但是,它只是支持普通的字符串類型進(jìn)行編碼和解碼而已,對于復(fù)雜的引用類型則無效。
3.2 大體流程
其實原理是非常的簡單的,請看下圖。
執(zhí)行流程說明:
- StringDecoder 必須放在業(yè)務(wù) Handler 之前,因為都是 InboundHandler,需要按順序執(zhí)行;
- StringEncoder 放在業(yè)務(wù) Handler 之前,則可以使用 ctx.writeAndFlush () 輸出數(shù)據(jù),也可以使用 ctx.channel ().writeAndFlus () 輸出數(shù)據(jù)(ChannelHandler 已經(jīng)講過原理了);
- StringEncoder 放在業(yè)務(wù) Handler 之后,則只能使用
ctx.channel().writeAndFlush()
輸出數(shù)據(jù)。
3.3 源碼閱讀
思考:StringDecoder 和 StringEncoder 到底怎么實現(xiàn)的呢?
StringDecoder 源碼:
@Sharable
public class StringDecoder extends MessageToMessageDecoder<ByteBuf> {
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
//直接msg.toString()
out.add(msg.toString(this.charset));
}
}
發(fā)現(xiàn) StringDecoder 的源碼非常的簡單,直接.toString()
轉(zhuǎn)換即可。
StringEncoder 源碼:
@Sharable
public class StringEncoder extends MessageToMessageEncoder<CharSequence> {
protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
if (msg.length() != 0) {
//繼續(xù)跟進(jìn)源碼
out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), this.charset));
}
}
}
public static ByteBuf encodeString(ByteBufAllocator alloc, CharBuffer src, Charset charset) {
//繼續(xù)跟進(jìn)源碼
return encodeString0(alloc, false, src, charset, 0);
}
保留核心源碼
static ByteBuf encodeString0(ByteBufAllocator alloc, boolean enforceHeap, CharBuffer src, Charset charset, int extraCapacity) {
CharsetEncoder encoder = CharsetUtil.encoder(charset);
int length = (int)((double)src.remaining() * (double)encoder.maxBytesPerChar()) + extraCapacity;
boolean release = true;
//1.創(chuàng)建ByteBuf分配器
ByteBuf dst;
if (enforceHeap) {
dst = alloc.heapBuffer(length);
} else {
dst = alloc.buffer(length);
}
ByteBuf var12;
try {
//2.得到NIO的ByteBuffer【跟進(jìn)Netty的ByteBuf基本上一樣】
ByteBuffer dstBuf = dst.internalNioBuffer(0, length);
int pos = dstBuf.position();
//3.把內(nèi)容寫得NIO的ByteBuffer
CoderResult cr = encoder.encode(src, dstBuf, true);
cr = encoder.flush(dstBuf);
//4.更新ByteBuf的寫指針writeIndex
dst.writerIndex(dst.writerIndex() + dstBuf.position() - pos);
//5.給var12賦值
var12 = dst;
} catch (CharacterCodingException var16) {
throw new IllegalStateException(var16);
}
return var12;
}
大致流程就是把字符串內(nèi)容轉(zhuǎn)換成 NIO 的 ByteBuffer,這里大致知道整個流程即可,不用深究每行代碼的意思,其實 Netty 的 ByteBuf 底層就是基于 ByteBuffer 進(jìn)行封裝的。
3. 自定義編解碼器
通過上面 Demo 的學(xué)習(xí),以及 StringDecoder 和 StringEncoder 兩個類的學(xué)習(xí),相信大家更加能理解編解碼器了,畢竟 StringDecoder 和 StringEncoder 從字面意思也能理解它們是針對字符串格式的,如果我們想要傳遞一個實體那么怎么辦呢?
主要解決方案有兩種:
方案一: 把實體轉(zhuǎn)換成 json 格式字符串,然后依然使用 StringDecoder 和 StringEncoder 編解碼器,但是每次手工轉(zhuǎn)換和解析,非常的麻煩;
方案二: 自定義針對實體的編解碼器,并且加入到雙向鏈表里面,這樣就可以傳遞自定義實體了。
下面主要講解如何實現(xiàn)針對實體的編解碼器:
3.1 實體
實例:
@Data
public class User {
private String name;
private Integer age;
}
3.2 編碼器
核心步驟:
- 繼承 MessageToByteEncoder,重寫 encode 方法;
- 把 User 對象轉(zhuǎn)換成 byte [];
- 把 byte [] 寫到 ByteBuf。
實例:
public class MyEncoder extends MessageToByteEncoder<User> {
protected void encode(ChannelHandlerContext channelHandlerContext,
User user,
ByteBuf byteBuf) throws Exception {
//1.對象流
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(user);
byte[] bytes=os.toByteArray();
//2.關(guān)閉流
oos.close();
os.close();
//3.寫到ByteBuf容器
byteBuf.writeBytes(bytes);
}
}
3.3 解碼器
核心步驟:
- 繼承 ByteToMessageDecoder,重寫 decode 方法;
- 自定義一個 byte [] 數(shù)組,長度是 ByteBuf 的可讀長度;
- 把 ByteBuf 轉(zhuǎn)換成 User 實體。
實例:
public class MyDecoder extends ByteToMessageDecoder {
protected void decode(ChannelHandlerContext channelHandlerContext,
ByteBuf byteBuf,
List<Object> list) throws Exception {
//1.定義byte[],長度為ByteBuf可讀長度
byte[] bytes=new byte[byteBuf.readableBytes()];
//2.往byte[]讀取數(shù)據(jù)
byteBuf.readBytes(bytes);
//3.對象流
ByteArrayInputStream is=new ByteArrayInputStream(bytes);
ObjectInputStream iss=new ObjectInputStream(is);
User user=(User)iss.readObject();
//4.關(guān)閉流
is.close();
iss.close();
//5.添加到集合
list.add(user);
}
}
3.4 添加到 Pipeline
實例:
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
//1.解碼器
ch.pipeline().addLast(new MyDecoder());
//2.編碼器
ch.pipeline().addLast(new MyEncoder());
//3.業(yè)務(wù)Handler
ch.pipeline().addLast(new ServerTestHandler());
}
});
4. 小結(jié)
通常情況下,需要把編解碼器分別獨立封裝成 Handler,并且加入到 ChannelPipeline 進(jìn)行管理,主要目的是簡化繁瑣的編碼和解碼的步驟,讓業(yè)務(wù) Handler 更加專注去處理業(yè)務(wù)邏輯,更加的符合開發(fā)人員的習(xí)慣。
本節(jié)主要掌握以下兩點內(nèi)容
- 如果針對字符串,那么可以使用 Netty 內(nèi)置的編解碼器,分別是 StringEncoder 和 StringDecoder;
- 如果是其它引用類型,主要有兩種方式,①轉(zhuǎn)換成字符串格式;②自定義編解碼器。