Dubbo中編碼和解碼的解析

(這里做的解析不是很詳細,等到走完整個流程再來解析)Dubbo中編解碼的工作由Codec2接口的實現來處理,回想一下第一次接觸到Codec2相關的內容是在服務端暴露服務的時候,根據具體的協議去暴露服務的步驟中,在DubboProtocol的createServer方法中:

private ExchangeServer createServer(URL url) {
    。。。
    //這里url會添加codec=dubbo
    url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
    ExchangeServer server;
    try {
        server = Exchangers.bind(url, requestHandler);
    }
    。。。
    return server;
}

緊接著進入Exchangers.bind(url, requestHandler);

public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    //如果url中沒有codec屬性,就會添加codec=exchange
    url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    return getExchanger(url).bind(url, handler);
}

然后會繼續進入HeaderExchanger的bind方法:

public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}

在這里會創建一個DecodeHandler實例。繼續跟蹤Transporters的bind方法,會發現直接返回一個NettyServer實例,在NettyServer的父類AbstractEndpoint構造方法初始的時候,會根據url獲取一個ChannelCodec,并將其賦值給codec存放到NettyServer的實例中。

我們先看下getChannelCodec(url);方法:

protected static Codec2 getChannelCodec(URL url) {
    //獲取codecName,不存在的話,默認為telnet
    String codecName = url.getParameter(Constants.CODEC_KEY, "telnet");
    //先看下是不是Codec2的實現,是的話就根據SPI擴展機制獲得Codec2擴展的實現
    //我們這里默認使用的是DubboCountCodec
    if (ExtensionLoader.getExtensionLoader(Codec2.class).hasExtension(codecName)) {
        return ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(codecName);
    } else {
        //如果不是Codec2的實現,就去查找Codec的實現
        //然后使用CodecAdapter適配器類來轉換成Codec2
        return new CodecAdapter(ExtensionLoader.getExtensionLoader(Codec.class)
                                           .getExtension(codecName));
    }
}

這里返回的是Codec2,而Codec這個接口已經被標記為過時。到這里的話,在NettyServer中就會存在一個Codec2的實例了。

在繼續往下看到NettyServer中的doOpen()方法,這里是使用Netty的邏輯打開服務并綁定監聽服務的地方:

protected void doOpen() throws Throwable {
    NettyHelper.setNettyLoggerFactory();
    ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
    ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
    ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
    bootstrap = new ServerBootstrap(channelFactory);

    final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
    channels = nettyHandler.getChannels();
    bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
        public ChannelPipeline getPipeline() {
            //這里的getCodec方法獲取到的codec就是在AbstractEndpoint中我們獲取到的codec
            //NettyCodecAdapter,適配器類
            NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec() ,getUrl(), NettyServer.this);
            ChannelPipeline pipeline = Channels.pipeline();
            pipeline.addLast("decoder", adapter.getDecoder());//SimpleChannelUpstreamHandler
            pipeline.addLast("encoder", adapter.getEncoder());//OneToOneEncoder
            pipeline.addLast("handler", nettyHandler);
            return pipeline;
        }
    });
    // bind
    channel = bootstrap.bind(getBindAddress());
}

這里就在Netty的pipeline中添加了編解碼器。這里涉及到Netty的相關流程,可以先了解下Netty3服務端流程簡介

decoder為解碼器,是一個SimpleChannelUpstreamHandler,從Socket到Netty中的時候,需要解碼,也就是服務提供端接收到消費者的請求的時候,需要解碼。

encoder是編碼器,是OneToOneEncoder,這個類實現了ChannelDownstreamHandler,從服務提供端發送給服務消費者的時候,需要編碼。

nettyHandler實現了ChannelUpstreamHandler, ChannelDownstreamHandler兩個,上下的時候都需要處理。

接收到服務消費者的請求的時候,會先執行decoder,然后執行nettyHandler。

發送給消費者的時候,會先執行nettyHandler,然后執行encoder。

dubbo協議頭

協議頭是16字節的定長數據:

  • 2字節short類型的Magic

  • 1字節的消息標志位

    • 5位序列化id
    • 1位心跳還是正常請求
    • 1位雙向還是單向
    • 1位請求還是響應
  • 1字節的狀態位

  • 8字節的消息id

  • 4字節數據長度

編碼的過程

首先會判斷是請求還是響應,代碼在ExchangeCodec的encode方法:

public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
    if (msg instanceof Request) {//Request類型
        encodeRequest(channel, buffer, (Request) msg);
    } else if (msg instanceof Response) {//Response類型
        encodeResponse(channel, buffer, (Response) msg);
    } else {//telenet類型的
        super.encode(channel, buffer, msg);
    }
}

服務提供者對響應信息編碼

在服務提供者端一般是對響應來做編碼,所以這里重點看下encodeResponse。

encodeResponse:

protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException {
    try {
        //序列化方式
        //也是根據SPI擴展來獲取,url中沒指定的話默認使用hessian2
        Serialization serialization = getSerialization(channel);
        //長度為16字節的數組,協議頭
        byte[] header = new byte[HEADER_LENGTH];
        //魔數0xdabb
        Bytes.short2bytes(MAGIC, header);
        //序列化方式
        header[2] = serialization.getContentTypeId();
        //心跳消息還是正常消息
        if (res.isHeartbeat()) header[2] |= FLAG_EVENT;
        //響應狀態
        byte status = res.getStatus();
        header[3] = status;
        //設置請求id
        Bytes.long2bytes(res.getId(), header, 4);
        //buffer為1024字節的ChannelBuffer
        //獲取buffer的寫入位置
        int savedWriteIndex = buffer.writerIndex();
        //需要再加上協議頭的長度之后,才是正確的寫入位置
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
        ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
        ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
        // 對響應信息或者錯誤消息進行編碼
        if (status == Response.OK) {
            if (res.isHeartbeat()) {
                //心跳
                encodeHeartbeatData(channel, out, res.getResult());
            } else {
                //正常響應
                encodeResponseData(channel, out, res.getResult());
            }
        }
        //錯誤消息
        else out.writeUTF(res.getErrorMessage());
        out.flushBuffer();
        bos.flush();
        bos.close();
        //寫出去的消息的長度
        int len = bos.writtenBytes();
        //查看消息長度是否過長
        checkPayload(channel, len);
        Bytes.int2bytes(len, header, 12);
        //重置寫入的位置
        buffer.writerIndex(savedWriteIndex);
        //向buffer中寫入消息頭
        buffer.writeBytes(header); // write header.
        //buffer寫出去的位置從writerIndex開始,加上header長度,加上數據長度
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
    } catch (Throwable t) {
        // 發送失敗信息給Consumer,否則Consumer只能等超時了
        if (! res.isEvent() && res.getStatus() != Response.BAD_RESPONSE) {
            try {
                // FIXME 在Codec中打印出錯日志?在IoHanndler的caught中統一處理?
                logger.warn("Fail to encode response: " + res + ", send bad_response info instead, cause: " + t.getMessage(), t);

                Response r = new Response(res.getId(), res.getVersion());
                r.setStatus(Response.BAD_RESPONSE);
                r.setErrorMessage("Failed to send response: " + res + ", cause: " + StringUtils.toString(t));
                channel.send(r);

                return;
            } catch (RemotingException e) {
                logger.warn("Failed to send bad_response info back: " + res + ", cause: " + e.getMessage(), e);
            }
        }

        // 重新拋出收到的異常
        if (t instanceof IOException) {
            throw (IOException) t;
        } else if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else if (t instanceof Error) {
            throw (Error) t;
        } else  {
            throw new RuntimeException(t.getMessage(), t);
        }
    }
}

服務消費者對請求信息編碼

消費者端暫先不做解析

解碼的過程

服務提供者對請求消息的解碼

decode方法一次只會解析一個完整的dubbo協議包,但是每次收到的協議包不一定是完整的,或者有可能是多個協議包。看下代碼解析,首先看NettyCodecAdapter的內部類InternalDecoder的messageReceived方法:

public void messageReceived(ChannelHandlerContext ctx, MessageEvent event) throws Exception {
    Object o = event.getMessage();
    if (! (o instanceof ChannelBuffer)) {
        ctx.sendUpstream(event);
        return;
    }

    ChannelBuffer input = (ChannelBuffer) o;
    int readable = input.readableBytes();
    if (readable <= 0) {
        return;
    }

    com.alibaba.dubbo.remoting.buffer.ChannelBuffer message;
    if (buffer.readable()) {
        if (buffer instanceof DynamicChannelBuffer) {
            buffer.writeBytes(input.toByteBuffer());
            message = buffer;
        } else {
            int size = buffer.readableBytes() + input.readableBytes();
            message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.dynamicBuffer(
                size > bufferSize ? size : bufferSize);
            message.writeBytes(buffer, buffer.readableBytes());
            message.writeBytes(input.toByteBuffer());
        }
    } else {
        message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.wrappedBuffer(
            input.toByteBuffer());
    }

    NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
    Object msg;
    //讀索引
    int saveReaderIndex;
    try {
        do {
            saveReaderIndex = message.readerIndex();
            try {
            //解碼
                msg = codec.decode(channel, message);
            } catch (IOException e) {
                buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
                throw e;
            }
            //不完整的協議包
            if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {
                //重置讀索引
                message.readerIndex(saveReaderIndex);
                //跳出循環,之后在finally中把message賦值給buffer保存起來,等到下次接收到數據包的時候會追加到buffer的后面
                break;
            } else {//有多個協議包,觸發messageReceived事件
                if (saveReaderIndex == message.readerIndex()) {
                    buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
                    throw new IOException("Decode without read data.");
                }
                if (msg != null) {
                    Channels.fireMessageReceived(ctx, msg, event.getRemoteAddress());
                }
            }
        } while (message.readable());
    } finally {
        if (message.readable()) {
            message.discardReadBytes();
            buffer = message;
        } else {
            buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
        }
        NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
    }
}

繼續看codec.decode(channel, message);這里是DubboCountCodec的decode方法:

public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
    //當前的讀索引記錄下來
    int save = buffer.readerIndex();
    //多消息
    MultiMessage result = MultiMessage.create();
    do {
        //解碼消息
        Object obj = codec.decode(channel, buffer);
        //不是完整的協議包
        if (Codec2.DecodeResult.NEED_MORE_INPUT == obj) {
            buffer.readerIndex(save);
            break;
        } else {//多個協議包
            result.addMessage(obj);
            logMessageLength(obj, buffer.readerIndex() - save);
            save = buffer.readerIndex();
        }
    } while (true);
    if (result.isEmpty()) {
        return Codec2.DecodeResult.NEED_MORE_INPUT;
    }
    if (result.size() == 1) {
        return result.get(0);
    }
    return result;
}

繼續看ExchangeCodec的decode方法:

public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
    //可讀字節數
    int readable = buffer.readableBytes();
    byte[] header = new byte[Math.min(readable, HEADER_LENGTH)];
    //協議頭
    buffer.readBytes(header);
    //解碼
    return decode(channel, buffer, readable, header);
}

解碼decode:

protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException {
    //檢查魔數.
    if (readable > 0 && header[0] != MAGIC_HIGH 
            || readable > 1 && header[1] != MAGIC_LOW) {
        int length = header.length;
        if (header.length < readable) {
            header = Bytes.copyOf(header, readable);
            buffer.readBytes(header, length, readable - length);
        }
        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);
                header = Bytes.copyOf(header, i);
                break;
            }
        }
        //telenet
        return super.decode(channel, buffer, readable, header);
    }
    //不完整的包
    if (readable < HEADER_LENGTH) {
        return DecodeResult.NEED_MORE_INPUT;
    }

    //數據長度
    int len = Bytes.bytes2int(header, 12);
    checkPayload(channel, len);

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

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

    try {
        //解碼數據
        return decodeBody(channel, is, header);
    } finally {
        if (is.available() > 0) {
            try {
                StreamUtils.skipUnusedStream(is);
            } catch (IOException e) { }
        }
    }
}

decodeBody解析數據部分:

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);
    //反序列化
    ObjectInput in = s.deserialize(channel.getUrl(), is);
    //獲取請求id
    long id = Bytes.bytes2long(header, 4);
    //這里是解碼響應數據
    if ((flag & FLAG_REQUEST) == 0) {
        //response的id設為來時候的Request的id,這樣才能對上暗號
        Response res = new Response(id);
        //判斷是什么類型請求
        if ((flag & FLAG_EVENT) != 0) {
            res.setEvent(Response.HEARTBEAT_EVENT);
        }
        //獲取狀態
        byte status = header[3];
        res.setStatus(status);
        if (status == Response.OK) {
            try {
                Object data;
                if (res.isHeartbeat()) {
                    //解碼心跳數據
                    data = decodeHeartbeatData(channel, in);
                } else if (res.isEvent()) {
                    //事件
                    data = decodeEventData(channel, in);
                } else {
                    //響應
                    data = decodeResponseData(channel, in, getRequestData(id));
                }
                res.setResult(data);
            } catch (Throwable t) {
                res.setStatus(Response.CLIENT_ERROR);
                res.setErrorMessage(StringUtils.toString(t));
            }
        } else {
            res.setErrorMessage(in.readUTF());
        }
        return res;
    } else {//這是解碼請求數據
        // request的id
        Request req = new Request(id);
        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, in);
            } else if (req.isEvent()) {
                //事件
                data = decodeEventData(channel, in);
            } else {
                //請求
                data = decodeRequestData(channel, in);
            }
            req.setData(data);
        } catch (Throwable t) {
            // bad request
            req.setBroken(true);
            req.setData(t);
        }
        return req;
    }
}

具體的解碼細節交給底層解碼器,這里是使用的hessian2。

服務消費者對響應消息的解碼

暫先不做解釋。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容