<2021SC@SDUSC>netty编解码器

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,分别用于处理出站数据与入站数据,之后,又简单的分析了它们处理的逻辑。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东羚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值