Netty5的CodeC编解码对以往的版本进行了简化,没有单独的Encoder / Decoder接口,都继承了ChannelHandlerApdater类,来实现ChannelHandler接口。
对Decoder来说,主要有两个顶层的抽象类,一个是从字节流到消息的ByteToMessageDecoder,一个是中间消息到业务消息的MessageToMessageDecoder。
ByteToMessageDecoder放置在MessageToMessageDecoder前面,处理inbound事件。
拿读IO数据来举例,最初的数据来源与底层的SocketChannel的读方法,比如UnpooledDirectByteBuf,先分配一个直接内存缓冲区DirectByteBuffer,然后把数据复制到ByteBuf,返回ByteBuf给Netty上层的类。
对UnpooledDirectByteBuf来说,它底层封装了Java的ByteBuffer,使用ByteBuf来对ByteBuffer进行操作,并发ByteBuf抛给顶层
// NioSocketChannel.doReadBytes
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
return byteBuf.writeBytes(javaChannel(), byteBuf.writableBytes());
}
// AbstractByteBuf.writeBytes
public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
ensureWritable(length);
int writtenBytes = setBytes(writerIndex, in, length);
if (writtenBytes > 0) {
writerIndex += writtenBytes;
}
return writtenBytes;
}
//UnpooledDirectByteBuf.setBytes
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
ensureAccessible();
ByteBuffer tmpBuf = internalNioBuffer();
tmpBuf.clear().position(index).limit(index + length);
try {
return in.read(tmpNioBuf);
} catch (ClosedChannelException e) {
return -1;
}
}
Netty框架拿到ByteBuf之后就拿到了读到的字节数据,就可以调用ByteToMessageDecoder来把字节流转化成业务消息。把字节流转化成业务消息也就是如何分帧的问题。具体有几类:
1. 固定长度来分帧
2.根据制定的分隔符
3. 采用消息头+消息体的方式,消息头制定消息长度
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
RecyclableArrayList out = RecyclableArrayList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
first = cumulation == null;
if (first) {
cumulation = data;
} else {
if (cumulation.writerIndex() > cumulation.maxCapacity() - data.readableBytes()) {
expandCumulation(ctx, data.readableBytes());
}
cumulation.writeBytes(data);
data.release();
}
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Throwable t) {
throw new DecoderException(t);
} finally {
if (cumulation != null && !cumulation.isReadable()) {
cumulation.release();
cumulation = null;
}
int size = out.size();
decodeWasNull = size == 0;
for (int i = 0; i < size; i ++) {
ctx.fireChannelRead(out.get(i));
}
out.recycle();
}
} else {
ctx.fireChannelRead(msg);
}
}
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
while (in.isReadable()) {
int outSize = out.size();
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);
}
}
把字节流转化成消息后,这个消息有时候是中间消息,还需要把中间消息转化成具体的业务消息,这时候调用MessageToMessageDecoder接口。
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
RecyclableArrayList out = RecyclableArrayList.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 {
int size = out.size();
for (int i = 0; i < size; i ++) {
ctx.fireChannelRead(out.get(i));
}
out.recycle();
}
}
// HttpContentDecoder的decode方法,把HttpResponse消息转化成具体的业务消息
protected void decode(ChannelHandlerContext ctx, HttpObject msg, List<Object> out) throws Exception {
if (msg instanceof HttpResponse && ((HttpResponse) msg).getStatus().code() == 100) {
if (!(msg instanceof LastHttpContent)) {
continueResponse = true;
}
// 100-continue response must be passed through.
out.add(ReferenceCountUtil.retain(msg));
return;
}
if (continueResponse) {
if (msg instanceof LastHttpContent) {
continueResponse = false;
}
// 100-continue response must be passed through.
out.add(ReferenceCountUtil.retain(msg));
return;
}
if (msg instanceof HttpMessage) {
assert message == null;
message = (HttpMessage) msg;
decodeStarted = false;
cleanup();
}
最后当ChannelPipeline到达后端的业务ChannelHandler时,拿到的消息是已经解码后的业务消息。
编码的过程也是一样,提供两个顶层接口,
1. MessageToMessageEncoder负责把业务消息转化成中间消息
2. MessageToByteEncoder负责把中间消息/业务消息转化成字节流
编码过程是一个反向的过程,这里就不重复展示代码了。MessgeToMessageEncoder/Decoder不是必须的组件,可以根据实际情况使用。
ByteToMessageDecoder / MessageToByteEncoder 是必须的组件
Netty5默认提供支持ProtoBuf, JBoss Marshalling,Java序列化等具体的编解码技术,用来把Java对象变成字节流。具体的例子请参考 http://blog.csdn.net/iter_zc/article/details/39317311