2021SC@SDUSC
前言
在之后的几篇博客中,将会主要介绍netty的编解码器,并且会介绍几种常见的编码器、解码器,以及它们的使用场合。
编解码器的作用就是将原始的数据与目标数据的格式进行互相转换。在网络中,数据以bit流的形式来传输,encoder编码器负责将数据转换成适合传输的格式,而decoder解码器负责将传输的数据还原成原先的数据,在本篇博客中,将会看到,实际上,encoder、decoder都是ChannelHandler。
一、Encoder
以StringEncoder.java为例,下图是StringEncoder类的继承体系。
在图中,可以看到,StringEncoder继承自MessageToMessageEncoder<T>类,而该类继承自ChannelOutBoundHandlerAdapter类,因此,Encoder本身就是一个ChannelHandler,并且负责处理出站的数据。
现在,再简单分析一下MessageToMessageEncoder<T>类。
package io.netty.handler.codec;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
import io.netty.util.concurrent.PromiseCombiner;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.TypeParameterMatcher;
import java.util.List;
public abstract class MessageToMessageEncoder<I> extends ChannelOutboundHandlerAdapter {
private final TypeParameterMatcher matcher;
protected MessageToMessageEncoder() {
matcher = TypeParameterMatcher.find(this, MessageToMessageEncoder.class, "I");
}
protected MessageToMessageEncoder(Class<? extends I> outboundMessageType) {
matcher = TypeParameterMatcher.get(outboundMessageType);
}
public boolean acceptOutboundMessage(Object msg) throws Exception {
return matcher.match(msg);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
CodecOutputList out = null;
try {
if (acceptOutboundMessage(msg)) {
out = CodecOutputList.newInstance();
@SuppressWarnings("unchecked")
I cast = (I) msg;
try {
encode(ctx, cast, out);
} finally {
ReferenceCountUtil.release(cast);
}
if (out.isEmpty()) {
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) {
try {
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);
}
}
} finally {
out.recycle();
}
}
}
}
private static void writeVoidPromise(ChannelHandlerContext ctx, CodecOutputList out) {
final ChannelPromise voidPromise = ctx.voidPromise();
for (int i = 0; i < out.size(); i++) {
ctx.write(out.getUnsafe(i), voidPromise);
}
}
private static void writePromiseCombiner(ChannelHandlerContext ctx, CodecOutputList out, ChannelPromise promise) {
final PromiseCombiner combiner = new PromiseCombiner(ctx.executor());
for (int i = 0; i < out.size(); i++) {
combiner.add(ctx.write(out.getUnsafe(i)));
}
combiner.finish(promise);
}
protected abstract void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
}
在该类中,存在一个成员变量matcher,作为类型匹配器,在构造方法中完成初始化。另外有一个方法acceptOutboundMessage(Object msg),在方法中,使用matcher判断传入的msg是否是匹配的,返回值类型为boolean。
另外,有一个抽象方法encode(),负责将数据编码,这个方法需要由子类自己实现,在该类的writer方法中被使用,属于模板方法的设计模式。
在write()方法中,首先判断传入的msg是否是Encoder可以处理的类型,如果不是,将数据传递给下一个出站处理器,否则,将其加入到一个CodecOutputList中,变量名为out,之后,在fianlly语句块中,判断out是否为空,如果不是空,说明其中有需要处理的数据,判断其中的数量如果数量只有一个,直接传递给下一个处理器,否则,通过for循环,将每一个数据分别传给下一个处理器,这样的处理方式,避免了多个本来应该是不相干的数据黏在一起处理,实际上是在解决粘包的现象。
这个只是Encoder的大致的处理逻辑,有很多细节没有分析。
二、Decoder
以StringDecoder.java为例,下图是StringDecoder类的继承体系。
在图中,可以看到,StringDecoder继承自MessageToMessageDecoder<ByteBuf>类,而该类继承自ChannelInBoundHandlerAdapter类,与Encoder相似,Decoder本是也是一个ChannelHandler,只是,Decoder负责处理的是入站的数据。
现在,再看一下MessageToMessageEncoder<T>类。
package io.netty.handler.codec;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
import io.netty.util.internal.TypeParameterMatcher;
import java.util.List;
public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter {
private final TypeParameterMatcher matcher;
protected MessageToMessageDecoder() {
matcher = TypeParameterMatcher.find(this, MessageToMessageDecoder.class, "I");
}
protected MessageToMessageDecoder(Class<? extends I> inboundMessageType) {
matcher = TypeParameterMatcher.get(inboundMessageType);
}
public boolean acceptInboundMessage(Object msg) throws Exception {
return matcher.match(msg);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
CodecOutputList out = CodecOutputList.newInstance();
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
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 {
try {
int size = out.size();
for (int i = 0; i < size; i++) {
ctx.fireChannelRead(out.getUnsafe(i));
}
} finally {
out.recycle();
}
}
}
protected abstract void decode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
}
与Encoder相似,在该类中,也有一个成员变量matcher,类型是TypeParameterMatcher,用于判断需要读取的msg是否符合类型。同样有一个抽象方法decode(),由子类负责具体实现,而在channelRead()中调用了decode()方法。
而channelRead()的处理逻辑与Encoder的write()相似,都是先判断msg是否符合类型,如果符合,则将数据解码,加入到decoder中,而后,在finally语句块中,将list中的数据分别传递给下一个处理器。
三、问题
再上一篇博客中,写了一个群聊的demo,现在,有一个问题如下,再client1中写入了aaa\nbbb,发送给服务器,而服务器将消息转发给其它的client,在client2中受到消息aaa\nbbb,显然,\n是换行符,实际上期望的是发送的是两条消息aaa和bbb,而接收方收到的也是两条消息,关于这个问题,将会在之后的博客中解决。
四、总结
在本篇博客中,简单地介绍了netty的编解码器,知道了它们都是channelHandler,分别用于处理出站数据与入站数据,之后,又简单的分析了它们处理的逻辑。