通过上一节的分析,我们已经了解到,当Netty读取到客户端发送的数据之后是读取到了ByteBuf中,实际上就是字节,但是在java中一般传递过来的都是对象信息,发送端将待发送的数据序列化成字节然后通过网络发送出去,接收端将接收到的字节数据然后反序列化。
首先我们介绍下Netty中Inbound和Outbound编解码器:
MessageToMessageEncoder
和MessageToMessageEncoder
:
MessageToMessageDecoder
:对接收的数据进行解码,将接收的字节反序列化为需要的java对象,其实现channelRead
如下:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
CodecOutputList out = CodecOutputList.newInstance();
try {
if (acceptInboundMessage(msg)) {
I cast = (I) msg;
try {
decode(ctx, cast, out);
} finally {
ReferenceCountUtil.release(cast);
}
} else {
out.add(msg);
}
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
int size = out.size();
for (int i = 0; i < size; i ++) {
ctx.fireChannelRead(out.getUnsafe(i));
}
out.recycle();
}
}
protected abstract void decode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
可以看到其通过模板方法
模式,在这里定义了解码的步骤,但是实际的解码工作decode
还是需要子类自己去实现,而这里我们也很清楚的看到,在finally块中调用ReferenceCountUtil.release(cast);
进行了内存的释放,所以不用担心内存泄漏。一般如果我们需要自己定义具体的解码逻辑的时候,前面如果没有特殊处理,这里的类型 I 就是我们前面说的读取数据时候Netty的ByteBuf
MessageToMessageEncoder
:对写入到网络中的数据进行编码序列化,将需要传输的java对象序列化为字节然后通过网络发送给对端,其实现write如下:
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
CodecOutputList out = null;
try {
if (acceptOutboundMessage(msg)) {
out = CodecOutputList.newInstance();
I cast = (I) msg;
try {
encode(ctx, cast, out);
} finally {
ReferenceCountUtil.release(cast);
}
if (out.isEmpty()) {
out.recycle();
out = null;
throw new EncoderException(
StringUtil.simpleClassName(this) + " must produce at least one message.");
}
} else {
ctx.write(msg, promise);
}
} catch (EncoderException e) {
throw e;
} catch (Throwable t) {
throw new EncoderException(t);
} finally {
if (out != null) {
final int sizeMinusOne = out.size() - 1;
if (sizeMinusOne == 0) {
ctx.write(out.getUnsafe(0), promise);
} else if (sizeMinusOne > 0) {
if (promise == ctx.voidPromise()) {
writeVoidPromise(ctx, out);
} else {
writePromiseCombiner(ctx, out, promise);
}
}
out.recycle();
}
}
}
protected abstract void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
可以看到这里和解码器一样,也是采用模板方法。
需要注意的一点是这里的acceptInboundMessage(msg)
和acceptOutboundMessage(msg)
是来判断传递的消息类型与当前编码器、解码器的类型是否一样,如果不一样,则不支持处理,将会给后续的Handler继续处理。
MessageToMessageCodec
这是一个抽象类,内部持有一个MessageToMessageEncoder
和一个MessageToMessageDecoder
的匿名内部实现类,但是encode
和decode
都是委托给MessageToMessageCodec
去实现,这个好处就是我们继承实现这个类能同时实现编码器和解码器的工作,不用搞两个类去继承实现MessageToMessageEncoder
和MessageToMessageDecoder
####ByteArrayEncoder
、ByteArrayDecoder
这两个类是用来处理字节数组的,将读取的数据解码为字节数组返回以及将写入的字节数组编码发送
ByteToMessageDecoder
、MessageToByteEncoder
这对编解码器就是将ByteBuf
解码为指定类型已经将指定类型编码为ByteBuf
,如果在Handler中没有指定其他编解码器,那么其实与MessageToMessageEncoder``MessageToMessageDecoder
作用一样,但是MessageToMessageEncoder``MessageToMessageDecoder
可以指定类型,如果我们需要将ByteBuf
先转换为A在转换为B,那么可以通过这两个组合实现
ReplayingDecoder
ReplayingDecoder也是用来解决网络通信中的粘包和拆包问题,但是如果是通过固定分隔符或者固定长度的消息,则不需要此实现。但是如果有一些需要接收到一些数据之后才能确定数据到哪结束的时候,则可以使用该解码器
LineBasedFrameDecoder
换行解码器,通过\r\n
或者\n
为分隔符进行解码
FixedLengthFrameDecoder
定长解码器,通过固定长度分隔消息
DelimiterBasedFrameDecoder
通过指定的分隔符进行解码
LengthFieldBasedFrameDecoder
从指定长度读取消息头中关于消息长度的字段,消息头定长解码器,通过指定消息中某一个段长度,读取器内容为消息长度来进行分割解码