dubbo源碼分析26 -- 網(wǎng)絡(luò)編解碼

在網(wǎng)絡(luò)傳輸中只將數(shù)據(jù)看作是原始的字節(jié)序列。然則,我們的應(yīng)用程序需要把這些字節(jié)序列組成有意義的信息。將應(yīng)用程序的數(shù)據(jù)轉(zhuǎn)換為網(wǎng)絡(luò)格式梆砸,以及將網(wǎng)絡(luò)格式轉(zhuǎn)換為應(yīng)用程序的數(shù)據(jù)的組件分別叫作編碼器和解碼器,同時(shí)具有這兩種功能的單一組件叫作編解碼器园欣。

1帖世、粘包 & 拆包

基于前面的分析我們知道 dubbo 的遠(yuǎn)程調(diào)用是基于 Netty 這個(gè) Nio 框架進(jìn)行基于 TCP/IP 的 Socket 通信。

TCP 是一個(gè)“流”協(xié)議沸枯,所謂流就是沒(méi)有界限的一串?dāng)?shù)據(jù)日矫。可以想像一個(gè)河里的流水是連成一片的绑榴,其間沒(méi)有分界線哪轿。TCP 底層并不了解上層業(yè)務(wù)數(shù)據(jù)的具體含義,它會(huì)根據(jù) TCP 緩沖區(qū)的實(shí)際情況進(jìn)行包的劃分翔怎,所以在業(yè)務(wù)上認(rèn)為窃诉,一個(gè)完整的包可能會(huì)被 TCP 拆分成多個(gè)包進(jìn)行發(fā)送。也有可能把多個(gè)小的包封裝成一個(gè)大的數(shù)據(jù)包發(fā)送赤套,這就是所謂的 TCP 粘包和拆包問(wèn)題飘痛。

1.1 TCP 粘包 & 拆包問(wèn)題說(shuō)明

下面就通過(guò)以下的圖來(lái)說(shuō)明 TCP 粘包與拆包問(wèn)題:

TCP 粘包 / 拆包問(wèn)題.png

假設(shè)客戶端分別發(fā)送了兩個(gè)數(shù)據(jù)包 D1 和 D2 給服務(wù)端,由于服務(wù)端一次讀取到的字節(jié)數(shù)據(jù)是不確定的容握,所以可能存在以下 4 種情況宣脉。

  • 服務(wù)端兩次讀取到了兩個(gè)獨(dú)立的數(shù)據(jù)包,分別是 D1 和 D2剔氏,沒(méi)有粘包和拆包
  • 服務(wù)端一次接收到了兩個(gè)數(shù)據(jù)包塑猖, D1 和 D2 粘合在一起,被稱為 TCP 粘包
  • 服務(wù)端分兩次讀取到了兩個(gè)數(shù)據(jù)包谈跛,第一次讀取到了完整的 D1 包和 D2 包的部分內(nèi)部羊苟,第二次讀取到了 D2 包的剩余內(nèi)部,這被稱為 TCP 拆包
  • 服務(wù)端兩次讀取到了兩個(gè)數(shù)據(jù)包币旧,第一次讀取到了 D1 包的部分內(nèi)部 D1_1践险,第二次讀取到了 D1 包的剩余內(nèi)部 D1_2 和 D2 包的整包猿妈。

如果此時(shí)服務(wù)端 TCP 接收滑窗非常小吹菱,而數(shù)據(jù)包 D1 和 D2 比較大 巍虫,很有可能會(huì)發(fā)生第五種可能,即服務(wù)端分多次才能將 D1 和 D2 包接收完全鳍刷,期間發(fā)生多次拆包占遥。

1.2 解決粘包 & 拆包

由于底層的 TCP 無(wú)法理解上層的業(yè)務(wù)數(shù)據(jù),所以在底層是無(wú)法保證數(shù)據(jù)包不被拆分和重組的输瓜。這個(gè)問(wèn)題只能通過(guò)上層的應(yīng)用協(xié)議棧設(shè)計(jì)來(lái)解決瓦胎,主流的解決方案如下:

  • 消息定長(zhǎng),例如每個(gè)報(bào)文的大小為固定長(zhǎng)度 200 字節(jié)尤揣,如果不夠搔啊,空位被空格。
  • 在包尾增加回車換行符進(jìn)行分割北戏,例如 TFP 協(xié)議
  • 將消息分為消息頭和消息體负芋,消息頭中包含表示消息總長(zhǎng)度(或者消息體長(zhǎng)度)的字段。

netty 對(duì)于前 2 種都有自己的實(shí)現(xiàn)嗜愈,而 dubbo 采用的是第 3 種來(lái)解決粘包與拆包問(wèn)題的旧蛾。

2、dubbo 自定義協(xié)議

Netty 對(duì)于開(kāi)發(fā)者而言蠕嫁,其實(shí)就是操作 ChannelHandler 這個(gè)組件锨天。之前我們分析了 dubbo 網(wǎng)絡(luò)請(qǐng)求的發(fā)送與接收是實(shí)現(xiàn)了 ChannelHandler 的 NettyServerHandler。針對(duì)于編解碼同樣也是實(shí)現(xiàn) ChannelHandler 來(lái)進(jìn)行的剃毒。

NettyServer#doOpen

    protected void doOpen() throws Throwable {
        bootstrap = new ServerBootstrap();

        bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
        workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
                new DefaultThreadFactory("NettyServerWorker", true));

        final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
        channels = nettyServerHandler.getChannels();

        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
                        ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
                                .addLast("decoder", adapter.getDecoder())
                                .addLast("encoder", adapter.getEncoder())
                                .addLast("handler", nettyServerHandler);
                    }
                });
        // bind
        ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
        channelFuture.syncUninterruptibly();
        channel = channelFuture.channel();

    }

在進(jìn)行服務(wù)暴露的時(shí)候病袄,在初始化的時(shí)候通過(guò) ChannelPipeline 添加編碼、解碼與處理請(qǐng)求響應(yīng)的具體 ChannelHandler 實(shí)現(xiàn)類迟赃。dubbo 編解碼的具體都是通過(guò) NettyCodecAdapter 來(lái)處理的陪拘。

下面我們來(lái)看一下 dubbo 的協(xié)議頭約定:

dubbo_protocol_header.jpg

dubbo 使用長(zhǎng)度為 16 的 byte 數(shù)組作為協(xié)議頭。1 個(gè) byte 對(duì)應(yīng) 8 位纤壁。所以 dubbo 的協(xié)議頭有 128 位 (也就是上圖的從 0 到 127)左刽。我們來(lái)看一下這 128 位協(xié)議頭分別代表什么意思。

  • 0 ~ 7 : dubbo 魔數(shù)((short) 0xdabb) 高位酌媒,也就是 (short) 0xda欠痴。
  • 8 ~ 15: dubbo 魔數(shù)((short) 0xdabb) 低位,也就是 (short) 0xbb秒咨。
  • 16 ~ 20:序列化 id(Serialization id)喇辽,也就是 dubbo 支持的序列化中的 contentTypeId,比如 Hessian2Serialization#ID 為 2
  • 21 :是否事件(event )
  • 22 : 是否 Two way 模式(Two way)雨席。默認(rèn)是 Two-way 模式菩咨,<dubbo:method> 標(biāo)簽的 return 屬性配置為false,則是oneway模式
  • 23 :標(biāo)記是請(qǐng)求對(duì)象還是響應(yīng)對(duì)象(Req/res)
  • 24 ~ 31:response 的結(jié)果響應(yīng)碼 ,例如 OK=20
  • 32 ~ 95:id(long)抽米,異步變同步的全局唯一ID特占,用來(lái)做consumer和provider的來(lái)回通信標(biāo)記。
  • 96 ~ 127: data length云茸,請(qǐng)求或響應(yīng)數(shù)據(jù)體的數(shù)據(jù)長(zhǎng)度也就是消息頭+請(qǐng)求數(shù)據(jù)的長(zhǎng)度是目。用于處理 dubbo 通信的粘包與拆包問(wèn)題。

我們就根據(jù)源碼來(lái)分析一下 dubbo 是如何進(jìn)行編解碼的标捺。

3懊纳、協(xié)議源碼分析

dubbo 的編解碼可以分為以下 4 個(gè)部分來(lái)分析:

  • consumer 請(qǐng)求編碼
  • consumer響應(yīng)結(jié)果解碼
  • provider 請(qǐng)求解碼
  • provider 響應(yīng)結(jié)果編碼

在 dubbo 進(jìn)行服務(wù)暴露的時(shí)候是通過(guò) NettyCodecAdapter 來(lái)獲取到需要添加的編碼器與解碼器。在 NettyCodecAdapter 里面定義內(nèi)部類 InternalEncoder (繼承 netty 中的 MessageToByteEncoder)實(shí)現(xiàn) dubbo 的自定義編碼器亡容,定義內(nèi)部類 ByteToMessageDecoder (繼承 netty 中的 ByteToMessageDecoder) 實(shí)現(xiàn) dubbo 自定義解碼器嗤疯。不管是自定義的編碼器還是解碼器最終都會(huì)調(diào)用到 dubbo 的 SPI 接口 Codec2 默認(rèn)使用 DubboCodec。下面就具體的分析一下 dubbo 這 4 個(gè)編解碼過(guò)程闺兢。

3.1 consumer 請(qǐng)求編碼

consumer 在請(qǐng)求 provider 的時(shí)候需要把 Request 對(duì)象轉(zhuǎn)化成 byte 數(shù)組身弊,所以它是一個(gè)需要編碼的過(guò)程。

Consumer#request#encode.jpg

3.2 consumer響應(yīng)結(jié)果解碼

consumer 在接收 provider 響應(yīng)的時(shí)候需要把 byte 數(shù)組轉(zhuǎn)化成 Response 對(duì)象列敲,所以它是一個(gè)需要解碼的過(guò)程阱佛。

Consumer#response#decode.jpg

3.3 provider 請(qǐng)求解碼

provider 在接收 consumer 請(qǐng)求的時(shí)候需要把 byte 數(shù)組轉(zhuǎn)化成 Request 對(duì)象,所以它是一個(gè)需要解碼的過(guò)程戴而。

Provider#request#decode.jpg

3.4 響應(yīng)結(jié)果編碼

provider 在處理完成 consumer 請(qǐng)求需要響應(yīng)結(jié)果的時(shí)候需要把 Response 對(duì)象轉(zhuǎn)化成 byte 數(shù)組凑术,所以它是一個(gè)需要編碼的過(guò)程。

Provider#response#encode.jpg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末所意,一起剝皮案震驚了整個(gè)濱河市淮逊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扶踊,老刑警劉巖泄鹏,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異秧耗,居然都是意外死亡备籽,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)分井,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)车猬,“玉大人,你說(shuō)我怎么就攤上這事尺锚≈槿颍” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵瘫辩,是天一觀的道長(zhǎng)伏嗜。 經(jīng)常有香客問(wèn)我坛悉,道長(zhǎng),這世上最難降的妖魔是什么承绸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任吹散,我火速辦了婚禮,結(jié)果婚禮上八酒,老公的妹妹穿的比我還像新娘。我一直安慰自己刃唐,他們只是感情好羞迷,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著画饥,像睡著了一般衔瓮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抖甘,一...
    開(kāi)封第一講書(shū)人閱讀 52,184評(píng)論 1 308
  • 那天热鞍,我揣著相機(jī)與錄音,去河邊找鬼衔彻。 笑死薇宠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的艰额。 我是一名探鬼主播澄港,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柄沮!你這毒婦竟也來(lái)了回梧?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤祖搓,失蹤者是張志新(化名)和其女友劉穎狱意,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拯欧,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡详囤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镐作。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纬纪。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖滑肉,靈堂內(nèi)的尸體忽然破棺而出包各,到底是詐尸還是另有隱情,我是刑警寧澤靶庙,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布问畅,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏护姆。R本人自食惡果不足惜矾端,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卵皂。 院中可真熱鬧秩铆,春花似錦、人聲如沸灯变。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)添祸。三九已至滚粟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刃泌,已是汗流浹背凡壤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耙替,地道東北人亚侠。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像俗扇,于是被迫代替她去往敵國(guó)和親盖奈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 在前面一篇博客中分享了 dubbo 在網(wǎng)絡(luò)通信當(dāng)中的 consumer 的發(fā)送以及接收原理狐援。通過(guò)集群容錯(cuò)最終選擇一...
    carl_zhao閱讀 948評(píng)論 0 2
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理钢坦,服務(wù)發(fā)現(xiàn),斷路器啥酱,智...
    卡卡羅2017閱讀 134,695評(píng)論 18 139
  • 簡(jiǎn)書(shū) 占小狼轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處镶殷,謝謝禾酱! 本文由臧秀濤撰稿,經(jīng)過(guò)R大潤(rùn)色绘趋,由占小狼傾情分享颤陶,這些分析總結(jié)道出了TLA...
    美團(tuán)Java閱讀 9,583評(píng)論 2 36
  • 看到老師發(fā)的倡議書(shū)已經(jīng)好幾天了,我遲遲未動(dòng)筆陷遮。因?yàn)檫@幾天期中考試滓走,弄得我們心情都不好。雖然看書(shū)聽(tīng)課一年多了...
    ytt木子閱讀 104評(píng)論 0 0