Netty学习之旅------源码分析Netty解码编码器实现原理,轻松拿到了阿里Java高级开发工程师的offer

}

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:可以得出如下重要结论,并且在我们实现自己的解码器时要特别注意:

  1. 如果没有成功从本次累积缓存区解码出需要的消息,则不能修改累积缓存区的readerIndex,writerIndex。

  2. 如果解码出合适的消息,则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开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后

由于篇幅有限,这里就不一一罗列了,20道常见面试题(含答案)+21条MySQL性能调优经验小编已整理成Word文档或PDF文档

MySQL全家桶笔记

还有更多面试复习笔记分享如下

Java架构专题面试复习

学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《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)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值