}
if (oldInputLength == in.readableBytes()) { //@6
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);
}
}
参数详解:
-
ChannelHandlerContext ctx 执行上下文。
-
ByteBuf in 当前累积的缓存区。
-
out 解码出消息的集合。
代码@1:如果当前累积区可读。
代码@2:当前解码后的消息条数,该值主要是判断本次解码,是否成功解码到消息。
代码@3:当前累积缓存区当前可读字节数。同样是用于判断是否成功解码。
代码@4:执行具体的解码实现(也成为编码解码协议的具体实现),该方法在具体的子类中实现。从这里也可以看出,整个ByteToMessageDecoder的设计,使用了模板模式。
代码@5:如果没有解码出消息(累积缓存区中的内容没有包含一个完整的请求信息,并且累积缓存区的可读数据没有发生变化,则结束本次解码。
代码@6:如果解码出消息,但是累积缓存区的可读数据没有发生变化,则抛出异常。
从代码@5,@6:可以得出如下重要结论,并且在我们实现自己的解码器时要特别注意:
-
如果没有成功从本次累积缓存区解码出需要的消息,则不能修改累积缓存区的readerIndex,writerIndex。
-
如果解码出合适的消息,则readerIndex,writerIndex要修改成已解析的字节的位置。
2.2.3 通道非激活事件实现 channelInactive
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
RecyclableArrayList out = RecyclableArrayList.newInstance();
try {
if (cumulation != null) {
callDecode(ctx, cumulation, out);
decodeLast(ctx, cumulation, out);
} else {
decodeLast(ctx, Unpooled.EMPTY_BUFFER, out);
}
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
try {
if (cumulation != null) {
cumulation.release();
cumulation = null;
}
int size = out.size();
for (int i = 0; i < size; i++) {
ctx.fireChannelRead(out.get(i));
}
if (size > 0) {
// Something was read, call fireChannelReadComplete()
ctx.fireChannelReadComplete();
}
ctx.fireChannelInactive();
} finally {
// recycle in all cases
out.recycle();
}
}
}
channelInActivie,通道变为非激活时触发的事件,处理逻辑就是如果当前的累积区有数据,则需要将数据解码并发送给下游handler,处理通道读相关事件。这里对有调用一个新的方法,decodeLast,表示通道转为非激活状态最后一次解码,可以供子类去实现。
关于ByteToMessageDecoder的实现原理就分析到这了,ByteToMessageDecoder可以是说是Netty提供的一个标签解码器的模板(典型的模板模式),用户可以基于此模板定制自己的私有协议。为了更好定制自己的解码器,接下来将重点分析Netty提供的一些解码器的实现。
3、LineBasedFrameDecoder 解码器实现分析
===============================
LineBasedFrameDecoder是基于行分割符符合的解码器。(\n 或\r\n)。
源码分析LineBasedFrameDecoder的解码实现。
/**
-
Create a frame out of the {@link ByteBuf} and return it.
-
@param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to
-
@param buffer the {@link ByteBuf} from which to read data
-
@return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could
-
be created.
*/
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
final int eol = findEndOfLine(buffer); //@1
if (!discarding) { //@2
if (eol >= 0) { //@3
final ByteBuf frame;
final int length = eol - buffer.readerIndex(); //@4
final int delimLength = buffer.getByte(eol) == ‘\r’? 2 : 1; //@5
if (length > maxLength) { //@6
buffer.readerIndex(eol + delimLength);
fail(ctx, length);
return null;
}
if (stripDelimiter) { // @7
frame = buffer.readSlice(length);
buffer.skipBytes(delimLength);
} else {
frame = buffer.readSlice(length + delimLength);
}
return frame.retain(); //@8
} else { //@9
final int length = buffer.readableBytes();
if (length > maxLength) { // @10
discardedBytes = length;
buffer.readerIndex(buffer.writerIndex());
discarding = true;
if (failFast) {
fail(ctx, "over " + discardedBytes);
}
}
return null;
}
} else {
if (eol >= 0) {
final int length = discardedBytes + eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == ‘\r’? 2 : 1;
buffer.readerIndex(eol + delimLength);
discardedBytes = 0;
discarding = false;
if (!failFast) {
fail(ctx, length);
}
} else {
discardedBytes = buffer.readableBytes();
buffer.readerIndex(buffer.writerIndex());
}
return null;
}
}
代码@1:从累积缓存区中,试图找到行结束符合(\r\n),具体实现再看。
代码@2:是否丢弃了一部分数据,当解码后的数据长度超过帧允许的最大长度时(maxLength)时,将丢弃整个累积缓存区。
代码@3:如果找到一个行结束标记,说明该累积缓存区中至少有一个完整的帧(请求信息),进入解码处理逻辑。
代码@4:计算该帧(请求信息)的长度,用eof减去当前累积区域的rederIndex即可。
代码@5:计算分割符所占用的字节长度,如果为\r\n则为两个字节,如果\n则表示1个字节。
代码@6:如果帧长度超过最大允许的长度,将累积缓存区的readerIndex设置为eof加上分隔符的长度,以便下次解码。同时触发exceptionCaught事件。
代码@7:根据是剥离分割符(剥离的话,就是该帧数据不会包含分割符合),从累积缓存区中读取一帧数据,使用的方式是 readSlice方法,共用累积缓存区的数据
代码@8:将解码处理的消息,引用加1,并返回处理,待交给下游Handler进一步处理。
代码@9,10:如果没有找到分割符,并且长度已经超过了maxLength的话,直接将该部分丢弃。
4、编码器实现原理(MessageToByteEncoder)
===============================
首先上文中提到了Netty解码器的实现原理,主要解决的问题是TCP的粘包,就是从请求流中解析出一个一个的客户端请求。解码器的职责是面向输入的,解析请求的(输入流)。而编码器,是面向响应的,将响应信息按照相关约定进行组织,方便接收端解析请求。上篇提到一个解码器(LineBasedFrameDecoder,请求信息以\n或\r\n),那是需要一个LineBasedFreameEncoder呢?答案是否定的,因为如果使用LineBasedFrameDecoder解码器解码请求信息的时候,与此响应报文中,肯定会以\n或\r\n结束,否则编码器将无法发挥作用,这也是编码器(响应流)会根据约定进行组织响应报文的原理。编码器的左右主要是实现自定义协议时需要用到的,重点实现ecode方法:下文给出MessageToByteEncoder的源码,由于实现原理简单,就不做过多讲解:
package io.netty.handler.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.TypeParameterMatcher;
/**
-
{@link ChannelHandlerAdapter} which encodes message in a stream-like fashion from one message to an
-
{@link ByteBuf}.
-
Example implementation which encodes {@link Integer}s to a {@link ByteBuf}.
-
public class IntegerEncoder extends {@link MessageToByteEncoder}<{@link Integer}> {
-
{@code @Override}
-
public void encode({@link ChannelHandlerContext} ctx, {@link Integer} msg, {@link ByteBuf} out)
-
throws {@link Exception} {
-
out.writeInt(msg);
-
}
-
}
*/
public abstract class MessageToByteEncoder extends ChannelHandlerAdapter {
private final TypeParameterMatcher matcher;
private final boolean preferDirect;
/**
- @see {@link #MessageToByteEncoder(boolean)} with {@code true} as boolean parameter.
*/
protected MessageToByteEncoder() {
this(true);
}
/**
- @see {@link #MessageToByteEncoder(Class, boolean)} with {@code true} as boolean value.
*/
protected MessageToByteEncoder(Class<? extends I> outboundMessageType) {
this(outboundMessageType, true);
}
/**
-
Create a new instance which will try to detect the types to match out of the type parameter of the class.
-
@param preferDirect {@code true} if a direct {@link ByteBuf} should be tried to be used as target for
-
the encoded messages. If {@code false} is used it will allocate a heap
-
{@link ByteBuf}, which is backed by an byte array.
*/
protected MessageToByteEncoder(boolean preferDirect) {
matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, “I”);
this.preferDirect = preferDirect;
}
/**
-
Create a new instance
-
@param outboundMessageType The tpye of messages to match
-
@param preferDirect {@code true} if a direct {@link ByteBuf} should be tried to be used as target for
-
the encoded messages. If {@code false} is used it will allocate a heap
-
{@link ByteBuf}, which is backed by an byte array.
*/
protected MessageToByteEncoder(Class<? extends I> outboundMessageType, boolean preferDirect) {
matcher = TypeParameterMatcher.get(outboundMessageType);
this.preferDirect = preferDirect;
}
/**
-
Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next
-
{@link ChannelHandler} in the {@link ChannelPipeline}.
*/
public boolean acceptOutboundMessage(Object msg) throws Exception {
return matcher.match(msg);
}
@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 {
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
最后
由于篇幅有限,这里就不一一罗列了,20道常见面试题(含答案)+21条MySQL性能调优经验小编已整理成Word文档或PDF文档
还有更多面试复习笔记分享如下
学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-FAsOS21R-1711150734998)]
[外链图片转存中…(img-7ECCPeFj-1711150734999)]
[外链图片转存中…(img-Rq5llvLB-1711150734999)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-Xlf5S9Tn-1711150735000)]
最后
由于篇幅有限,这里就不一一罗列了,20道常见面试题(含答案)+21条MySQL性能调优经验小编已整理成Word文档或PDF文档
[外链图片转存中…(img-qUzi0KOv-1711150735001)]
还有更多面试复习笔记分享如下
[外链图片转存中…(img-316M7wD1-1711150735002)]