netty下的编码器与解码器,并学习自定义编解码器
前言
继续学习,上几篇已经大致过完了handler的生命周期,inboundhandler与outboundhandler的方法在什么时候触发。现在学习一下encoder与decoder的内容(其实他们本质上还是handler)。
一、为什么要编码解码。
因为Socket传输数据大伙都知道是以bit进行传输的。然后通信的双方怎么能知道这些字节表示什么意思呢,所以就需要定一个通信协议实现双方沟通。然后将发送需要将信息进行按照协议的要求进行编码发送出去,接收方也按照协议的要求进行接收字节然后进行反编码提取还原信息。
二、netty下编码类。
编码类就是将信息转成Byte(1 Byte就是8 bit)。然后Encoder是就是一个outboundHandler。现在只学习它的父类
MessageToByteEncoder。
2.1MessageToByteEncoder类
2.1.1官方用例
学着官方的用例只需要继承MessageToByteEncoder类,然后重写encode方法即可。
2.1.2 TypeParameterMatcher类
就是用来保存要编码的类的类对象。以便于查找匹配后进行编码,保存的这个类对象就是那个 I。
2.1.3 MessageToByteEncoder下的write方法
待会看一下它在哪里调用。在这里acceptOutboundMessage方法就是用来判断该message是否是我们要接收的目标转换的对象类型。是就转换并且分配内存。并进行调用encode方法进行编码。如果不是就继续将对象向下传输(它认为也许有其他编码器对专门供不属于它的类型进行编码)。
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf buf = null;
try {
if (acceptOutboundMessage(msg)) {
@SuppressWarnings("unchecked")
I cast = (I) msg;
buf = allocateBuffer(ctx, cast, preferDirect);
try {
encode(ctx, cast, buf);
} finally {
ReferenceCountUtil.release(cast);
}
if (buf.isReadable()) {
ctx.write(buf, promise);
} else {
buf.release();
ctx.write(Unpooled.EMPTY_BUFFER, promise);
}
buf = null;
} else {
ctx.write(msg, promise);
}
} catch (EncoderException e) {
throw e;
} catch (Throwable e) {
throw new EncoderException(e);
} finally {
if (buf != null) {
buf.release();
}
}
}
这个类write方法对应的类是ByteToMessageCodec,而对象的encoder的write方法是继承MessagerToByteEncoder的
内部类。/(ㄒoㄒ)/~~,各种高级写法学不会了。(为啥要加个this ,有this跟没有this有啥区别,待会实验一下)
2.1.4 acceptOutboundMessage方法
就是通过matcher对象进行判断是否是该encoder能编码的类型。
三、netty下解码类。
解码器也是足够丰富的,所以学习肯定也学ByteToMessageCodec(先干趴祖先,它的子孙后代还不服服帖帖)。O(∩_∩)O
3.1 netty下解码类的用例。
注释也说了要重写这decode个方法,
3.2 decode方法
就一个抽象方法,必须得强制重写的啊,然后看一下谁调用了它
3.2 callDecode方法
这个方法调用了decode方法,它就确认数据源读入端in是否还有数据可读,然后也确认写出端out是否可以写出数据。
如果in还有数据可读,且out有数据就先将out对象向下传导给其他handler处理完,然后out清除数据,最后再重新从in里面读取数据进行解码写到out列表里,这个需要与开发者实现decode的抽象方法,在里面完成这么个解码与填充数据到out的动作。
在最后如果in对应的byteBuf里面的数据被读取完了,且out这个list也被正确地处理了就能正确地退出循环,否则有可能会报异常。
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
while (in.isReadable()) {
int outSize = out.size();
if (outSize > 0) {
fireChannelRead(ctx, out, outSize);
out.clear();
// Check if this handler was removed before continuing with decoding.
// If it was removed, it is not safe to continue to operate on the buffer.
//
// See:
// - https://github.com/netty/netty/issues/4635
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}
int oldInputLength = in.readableBytes();
decode(ctx, in, out);
// Check if this handler was removed before continuing the loop.
// If it was removed, it is not safe to continue to operate on the buffer.
//
// See https://github.com/netty/netty/issues/1664
if (ctx.isRemoved()) {
break;
}
if (outSize == out.size()) {
if (oldInputLength == in.readableBytes()) {
break;
} else {
continue;
}
}
if (oldInputLength == in.readableBytes()) {
throw new DecoderException(
StringUtil.simpleClassName(getClass()) +
".decode() did not read anything but decoded a message.");
}
if (isSingleDecode()) {
break;
}
}
} catch (DecoderException e) {
throw e;
} catch (Throwable cause) {
throw new DecoderException(cause);
}
}
3.3 channelRead方法
这个是inbound Handler的方法,当有数据可读的时候就会触发的方法,并且从head往tail进行传导。
它也判断这个msg是否是byteBuf对象,如果是就在当前进行尝试调用解码方法进行解码,如果不是就向下一个inboundhanlder进行传导
。
3.4 cumulator对象,
它就是确认cumulation是否需要扩容,以及将in的数据写入到cumulation对象持有的buffer并返回对应的句柄。
四、学习自定义编码器
就是将Message对象进行编码
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import personal.q_j_c.transporter.Message;
/**
* @author q_j_C
* @version 1.0
* @description
* @date 2021/5/29 16:08
*/
public class GenericEncoder extends MessageToByteEncoder<Message> {
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
byte[] head = msg.getHead();
byte[] body = msg.getBody();
byte[] data = new byte[head.length + body.length];
int i = msg.addByte2Array(data, 0, head);
msg.addByte2Array(data,i,body);
out.writeBytes(data);
}
}
五、学习自定义解码器
就是对message对象进行解码
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import org.apache.commons.collections4.CollectionUtils;
import personal.q_j_c.handler.constant.MessageType;
import personal.q_j_c.transporter.Message;
import personal.q_i_c.utils.ByteConvert;
import java.util.*;
/**
* @author q_i_c
* @version 1.0
* @description
* @date 2021/5/29 16:09
*/
public class GenericDecoder extends LengthFieldBasedFrameDecoder {
final private int HEAD_LENGTH =15;
private byte[] bytesRemain;
public GenericDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
}
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
Map<MessageType,List<Message>> messagesMap = new HashMap();
if(bytesRemain!=null){
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
buffer.writeBytes(bytesRemain);
buffer.writeBytes(in);
in.clear();
in=buffer;
bytesRemain=null;
}
while(in.readableBytes() >= HEAD_LENGTH&&in.refCnt()>0) {
// System.out.println(in.readerIndex());
Message message = parseByte2Message(in);
if(message!=null){
MessageType messageType = MessageType.getMessageType(message);
if(messageType!=null){
List<Message> messages = messagesMap.get(messageType);
if(CollectionUtils.isEmpty(messages)){
messages = new ArrayList<Message>();
messagesMap.put(messageType,messages);
}
messages = combineData(messages, message);
messagesMap.put(messageType,messages);
}
}
}
if(in.isReadable()){
bytesRemain=new byte[in.readableBytes()];
in.readBytes(bytesRemain,0,in.readableBytes());
in.clear();
}
return messagesMap;
}
private Message parseByte2Message(ByteBuf in){
ByteBuf markBuf = in.markReaderIndex();
Message message=null;
byte[] head = new byte[HEAD_LENGTH];
in.readBytes(head,0,HEAD_LENGTH);
byte[] bMagic = new byte[2];
byte[] bMsgLength=new byte[4];
byte[] bOrder=new byte[2];
byte[] bType=new byte[2];
byte isLast=0;
byte[] bInt=new byte[4];
for (int idx = 0; idx < head.length; idx++){
if(idx<2){
bMagic[idx]=head[idx];
}else if(idx<6){
bMsgLength[idx-2]=head[idx];
}else if(idx<8){
bOrder[idx-6]=head[idx];
}else if(idx<10){
bType[idx-8]=head[idx];
}else if(idx<11){
isLast=head[idx];
}else {
bInt[idx-11]=head[idx];
}
}
char magic = ByteConvert.byteToChar(bMagic);
int msgLength = ByteConvert.byteToInt(bMsgLength);
byte[] body = null;
short order = ByteConvert.byteToShort(bOrder);
short type = ByteConvert.byteToShort(bType);
int opt = ByteConvert.byteToInt(bInt);
if(magic!=0x08){
in.clear();
}else if(in.isReadable(msgLength)){
body = new byte[msgLength];
in.readBytes(body,0,msgLength);
message = new Message(order,type,isLast,opt,body);
}else {
bytesRemain=new byte[head.length+in.readableBytes()];
markBuf.resetReaderIndex();
markBuf.readBytes(bytesRemain);
}
return message;
}
/***
* @param messages
* @param curMessage
*/
private List<Message> combineData(List<Message> messages,Message curMessage){
List<Message> newMessages = new ArrayList<Message>();
newMessages.addAll(messages);
boolean isAdded=false;
if(CollectionUtils.isNotEmpty(messages)){
for (Message mainMessage : messages) {
if(mainMessage.getOp()==curMessage.getOp()){
int mLen = mainMessage.getBody().length;
int suLen = curMessage.getBody().length;
if( mainMessage.getOrder()<curMessage.getOrder()){
byte[] newBody= new byte[mLen+suLen];
int i = mainMessage.addByte2Array(newBody, 0, mainMessage.getBody());
mainMessage.addByte2Array(newBody,i,curMessage.getBody());
isAdded=true;
mainMessage.setBody(newBody);
}else if (mainMessage.getOrder()>curMessage.getOrder()){
byte[] newBody= new byte[mLen+suLen];
int i = mainMessage.addByte2Array(newBody, 0, curMessage.getBody());
mainMessage.addByte2Array(newBody,i,mainMessage.getBody());
mainMessage.setBody(newBody);
isAdded=true;
}
}
}
}
if(!isAdded){
newMessages.add(curMessage);
}
return newMessages;
}
}
六、定义字节转换工具类
一个用于将协议头的字节转换为基本类型或是,基本类型转成对应的字节。
/**
* @author q_j_c
* @version 1.0
* @description
* @date 2021/5/28 17:59
*/
public class ByteConvert {
private ByteConvert(){}
public static byte[] charToByte(char c) {
byte[] bytes = new byte[2];
for (int i=0 ;i<bytes.length;i++){
bytes[i] = (byte)((c >> (8*(bytes.length-1-i)) & 0xFF));
}
return bytes;
}
public static char byteToChar(byte[] b) {
char c = (char) (((b[0] & 0xFF) << 8) | (b[1] & 0xFF));
return c;
}
public static byte[] shortToByte(short s) {
byte[] bytes = new byte[2];
for (int i=0 ;i<bytes.length;i++){
bytes[i] = (byte)((s >> (8*(bytes.length-1-i)) & 0xFF));
}
return bytes;
}
public static short byteToShort(byte[] b) {
short c = (short) (((b[0] & 0xFF) << 8) | (b[1] & 0xFF));
return c;
}
public static byte[] intToByte(int Int) {
byte[] bytes = new byte[4];
for (int i=0 ;i<bytes.length;i++){
bytes[i] = (byte)((Int >> (8*(bytes.length-1-i)) & 0xFF));
}
return bytes;
}
public static int byteToInt(byte[] b){
int i= (b[0]<<24)&0xff000000|
(b[1]<<16)&0x00ff0000|
(b[2]<< 8)&0x0000ff00|
(b[3]<< 0)&0x000000ff;
return i;
}
}
总结
1.编码器与解码器本质上就是handler,只是框架帮是用者提前做了许多工作内容。
2.在进行自定义协议的时候,要约束好协议的head的长度,以及在head上必须带一个用于描述body的字节长度,否则在解码的时候无法获取正确的数据长度。