Spring Cloud Alibaba 教程 | Dubbo(十二):解码器

解码时序图

提供者服务和消费者服务都需要使用解码功能,下图是解码时序图:
在这里插入图片描述

  • InternalDecode:实现了NettyChannelHandler接口,是一个解码器实现类。
  • DubboCountCodec:将解码结果包装到MultiMessage
  • ExchangeCodec:负责解码报文的消息头数据。
  • DubboCodec:根据消息类型转发给DecodeableRpcInvocation或者DecodeableRpcResult进行消息体数据的解码。
  • DecodeableRpcResult:解码响应包的消息体数据。
  • DecodeableRpcInvocation:解码请求包的消息体数据。

解码详细过程

下面我们来源码分析Dubbo解码过程,进入InternalDecodemessageReceived()方法:

@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent event) throws Exception {
	//......省略部分代码

    NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
    Object msg;
    int saveReaderIndex;

    try {
        // decode object.
        do {
        	//message ChannelBuffer
            saveReaderIndex = message.readerIndex(); //@1
            try {
                msg = codec.decode(channel, message); //@2
            } catch (IOException e) {
                buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
                throw e;
            }
            if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {//@3
                message.readerIndex(saveReaderIndex);
                break;
            } else {
                if (saveReaderIndex == message.readerIndex()) {//@4
                    buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
                    throw new IOException("Decode without read data.");
                }
                if (msg != null) {//@5
                    Channels.fireMessageReceived(ctx, msg, event.getRemoteAddress());
                }
            }
        } while (message.readable());
    } finally {
        if (message.readable()) {//@6
            message.discardReadBytes();
            buffer = message;
        } else {
            buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
        }
        NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
    }
}

代码@1:message是一个ChannelBuffer,获取它的读指针下标赋值给saveReaderIndex。

代码@2:codec实例类型是DubboCountCodec,解码读取到的message内容。

代码@3:如果步骤@2的解码结果返回NEED_MORE_INPUT,表示message内容并不是一个完整的数据包(出现粘包拆包等问题),从新将readerIndex归位到saveReaderIndex

代码@4:如果两者相等,说明没有读取到数据,向外抛出异常信息。

代码@5:如果msg不为空,说明数据包已经解码成功,交给后面的Handler处理。

代码@6:message是用来存储读取到的数据的,当message不是完整数据包时如上面的步骤@3,这时候执行message.discardReadBytes()释放读坐标和写坐标都归0,同时赋值给缓冲区buffer,下次读取到数据时再赋值给message。

真正开始解码的过程在ExchangeCodec,我们进入它的decode()方法:

@Override
protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException {
    // check magic number.
    if (readable > 0 && header[0] != MAGIC_HIGH
            || readable > 1 && header[1] != MAGIC_LOW) {//@1
        int length = header.length;
        if (header.length < readable) {//@2
            header = Bytes.copyOf(header, readable);//@3
            buffer.readBytes(header, length, readable - length);//@4
        }
        for (int i = 1; i < header.length - 1; i++) {
            if (header[i] == MAGIC_HIGH && header[i + 1] == MAGIC_LOW) {
                buffer.readerIndex(buffer.readerIndex() - header.length + i);//@5
                header = Bytes.copyOf(header, i);//@6
                break;
            }
        }
        return super.decode(channel, buffer, readable, header);
    }
    // check length.
    if (readable < HEADER_LENGTH) {//@7
        return DecodeResult.NEED_MORE_INPUT;
    }

    // get data length.
    int len = Bytes.bytes2int(header, 12);//@8
    checkPayload(channel, len);

    int tt = len + HEADER_LENGTH;
    if (readable < tt) {//@9
        return DecodeResult.NEED_MORE_INPUT;
    }

    // limit input stream.
    ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len);

    try {
        return decodeBody(channel, is, header); //@10
    } finally {
        if (is.available() > 0) {
            try {
                if (logger.isWarnEnabled()) {
                    logger.warn("Skip input stream " + is.available());
                }
                StreamUtils.skipUnusedStream(is);
            } catch (IOException e) {
                logger.warn(e.getMessage(), e);
            }
        }
    }
}

代码@1:处理报文数据不是以魔法数0xdabb开头的情况。

代码@2:表示buffer中还有数据可以读取。

代码@3:重新分配给header数组长度延长到readable

代码@4:缓冲区buffer前面16个字节数据已经赋值给了header数组,所以这里将buffer剩余的部分也读取出来附加到header数组,这样缓冲区的内容就全部赋值给了header数组。

代码@5:循环header数组,找到相邻两个字节为魔法值的位置,将buffer的读下标指向魔法值的地方,即报文的开始位置。

代码@6:将header数组的开始位置到到魔法值之间的这段数据,又赋值给header数组。

代码@7:说明读取到的数据还不够,需要更多的数据。

代码@8:读取消息头的最后四个字节赋值给lenlen表示消息体的字节长度,接着执行checkPayload()检测消息体长度。

代码@9:tt代表了这个数据包的总长度,如果readable小于tt,说明读取到的数据包还不够。

代码@10:执行DubboCodecdecodeBody()方法,解码消息体。

进入DubboCodecdecodeBody()方法:

@Override
protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
    byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);
    Serialization s = CodecSupport.getSerialization(channel.getUrl(), proto);
    // get request id.
    long id = Bytes.bytes2long(header, 4);
    if ((flag & FLAG_REQUEST) == 0) {//消费者服务执行代码
        // decode response.
        Response res = new Response(id); //@1
        if ((flag & FLAG_EVENT) != 0) {
            res.setEvent(Response.HEARTBEAT_EVENT);
        }
        // get status.
        byte status = header[3]; //@2
        res.setStatus(status);
        if (status == Response.OK) {
            try {
                Object data;
                if (res.isHeartbeat()) {
                    data = decodeHeartbeatData(channel, deserialize(s, channel.getUrl(), is));
                } else if (res.isEvent()) {
                    data = decodeEventData(channel, deserialize(s, channel.getUrl(), is));
                } else {
                    DecodeableRpcResult result;
                    if (channel.getUrl().getParameter(
                            Constants.DECODE_IN_IO_THREAD_KEY,
                            Constants.DEFAULT_DECODE_IN_IO_THREAD)) {
                        result = new DecodeableRpcResult(channel, res, is,
                                (Invocation) getRequestData(id), proto);//@3
                        result.decode();
                    } else {
                        result = new DecodeableRpcResult(channel, res,
                                new UnsafeByteArrayInputStream(readMessageData(is)),
                                (Invocation) getRequestData(id), proto);
                    }
                    data = result;
                }
                res.setResult(data);
            } catch (Throwable t) {
                if (log.isWarnEnabled()) {
                    log.warn("Decode response failed: " + t.getMessage(), t);
                }
                res.setStatus(Response.CLIENT_ERROR);
                res.setErrorMessage(StringUtils.toString(t));
            }
        } else {
            res.setErrorMessage(deserialize(s, channel.getUrl(), is).readUTF());
        }
        return res;
    } else {//提供者服务执行代码
        // decode request.
        Request req = new Request(id); //@4
        req.setVersion("2.0.0");
        req.setTwoWay((flag & FLAG_TWOWAY) != 0);
        if ((flag & FLAG_EVENT) != 0) {
            req.setEvent(Request.HEARTBEAT_EVENT);
        }
        try {
            Object data;
            if (req.isHeartbeat()) {
                data = decodeHeartbeatData(channel, deserialize(s, channel.getUrl(), is));
            } else if (req.isEvent()) {
                data = decodeEventData(channel, deserialize(s, channel.getUrl(), is));
            } else {
                DecodeableRpcInvocation inv;
                if (channel.getUrl().getParameter(
                        Constants.DECODE_IN_IO_THREAD_KEY,
                        Constants.DEFAULT_DECODE_IN_IO_THREAD)) {
                    inv = new DecodeableRpcInvocation(channel, req, is, proto);//@5
                    inv.decode();
                } else {
                    inv = new DecodeableRpcInvocation(channel, req,
                            new UnsafeByteArrayInputStream(readMessageData(is)), proto);
                }
                data = inv;
            }
            req.setData(data);
        } catch (Throwable t) {
            if (log.isWarnEnabled()) {
                log.warn("Decode request failed: " + t.getMessage(), t);
            }
            // bad request
            req.setBroken(true);
            req.setData(t);
        }
        return req;
    }
}

代码@1:这里是消费者服务执行的代码,利用回传的请求id构造Response实例。

代码@2:获取响应报文的的状态值。

代码@3:表示直接在IO线程执行解码操作,响应报文交给DecodeableRpcResult处理。

代码@4:这里是提供者服务执行的代码,根据id构建Request实例。

代码@5:表示直接在IO线程执行解码操作,请求报文交给DecodeableRpcInvocation处理。

进入DecodeableRpcInvocationdecode()方法:

@Override
public Object decode(Channel channel, InputStream input) throws IOException {
    ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType)
            .deserialize(channel.getUrl(), input);

	//@1
    setAttachment(Constants.DUBBO_VERSION_KEY, in.readUTF());
    setAttachment(Constants.PATH_KEY, in.readUTF());
    setAttachment(Constants.VERSION_KEY, in.readUTF());

    setMethodName(in.readUTF());//@2
    try {
        Object[] args;
        Class<?>[] pts;
        String desc = in.readUTF();//@3
        if (desc.length() == 0) {
            pts = DubboCodec.EMPTY_CLASS_ARRAY;
            args = DubboCodec.EMPTY_OBJECT_ARRAY;
        } else {
            pts = ReflectUtils.desc2classArray(desc);
            args = new Object[pts.length];
            for (int i = 0; i < args.length; i++) {
                try {
                    args[i] = in.readObject(pts[i]);//@4
                } catch (Exception e) {
                    if (log.isWarnEnabled()) {
                        log.warn("Decode argument failed: " + e.getMessage(), e);
                    }
                }
            }
        }
        setParameterTypes(pts);

        Map<String, String> map = (Map<String, String>) in.readObject(Map.class);
        if (map != null && map.size() > 0) {//@5
            Map<String, String> attachment = getAttachments();
            if (attachment == null) {
                attachment = new HashMap<String, String>();
            }
            attachment.putAll(map);
            setAttachments(attachment);
        }
        //decode argument ,may be callback
        for (int i = 0; i < args.length; i++) {
            args[i] = decodeInvocationArgument(channel, this, pts, i, args[i]);
        }

        setArguments(args);

    } catch (ClassNotFoundException e) {
        throw new IOException(StringUtils.toString("Read invocation data failed.", e));
    } finally {
        if (in instanceof Cleanable) {
            ((Cleanable) in).cleanup();
        }
    }
    return this;
}

代码@1:按照Dubbo协议规定的消息体数据的顺序,依次读取Dubbo版本号、接口名称、接口版本。

代码@2:读取方法名称。

代码@3:读取方法参数类型。

代码@4:根据参数类型,依次读取方法参数值。

代码@5:读取额外的参数attachments,又被称为隐式参数。

进入DecodeableRpcResultdecode()方法:

@Override
public Object decode(Channel channel, InputStream input) throws IOException {
    ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType)
            .deserialize(channel.getUrl(), input);
    
    byte flag = in.readByte(); //@1
    switch (flag) {
        case DubboCodec.RESPONSE_NULL_VALUE: //@2
            break;
        case DubboCodec.RESPONSE_VALUE:
            try {
                Type[] returnType = RpcUtils.getReturnTypes(invocation);//@3
                setValue(returnType == null || returnType.length == 0 ? in.readObject() :
                        (returnType.length == 1 ? in.readObject((Class<?>) returnType[0])
                                : in.readObject((Class<?>) returnType[0], returnType[1])));//@4
            } catch (ClassNotFoundException e) {
                throw new IOException(StringUtils.toString("Read response data failed.", e));
            }
            break;
        case DubboCodec.RESPONSE_WITH_EXCEPTION:
            try {
                Object obj = in.readObject();
                if (obj instanceof Throwable == false)
                    throw new IOException("Response data error, expect Throwable, but get " + obj);
                setException((Throwable) obj);//@5
            } catch (ClassNotFoundException e) {
                throw new IOException(StringUtils.toString("Read response data failed.", e));
            }
            break;
        default://@6
            throw new IOException("Unknown result flag, expect '0' '1' '2', get " + flag);
    }
    if (in instanceof Cleanable) {
        ((Cleanable) in).cleanup();
    }
    return this;
}

代码@1:先读取一个字节赋值给flag,上一篇文章我们介绍过,提供者服务在将结果返回时,会先往消息体写一个字节代表消息响应类型。

代码@2:如果flag值为DubboCodec.RESPONSE_NULL_VALUE,表示响应报文没有返回值。

代码@3:如果flag值为DubboCodec.RESPONSE_VALUE,表示响应报文包含返回值,获取返回值类型。

代码@4:根据返回值类型,读取返回值。

代码@5:如果flag值为DubboCodec.RESPONSE_WITH_EXCEPTION,表示响应报文返回值是异常信息。

代码@6:不识别的消息响应类型,向外抛出IOException异常信息。

关注公众号了解更多原创博文

Alt

发布了122 篇原创文章 · 获赞 127 · 访问量 93万+

猜你喜欢

转载自blog.csdn.net/u010739551/article/details/104984335