結合RPC框架通訊談 netty如何解決TCP粘包問題

Yasin_Copeleft發表於2018-12-03

0.起因

因為自己造一個RPC框架的輪子時,需要解決TCP的粘包問題,特此記錄,希望方便他人。這是我寫的RPC框架的 GitHub地址 github.com/yangzhenkun…歡迎star,fork。已經寫了多篇文章對這個框架的原理進行說明。對原理有興趣的歡迎交流。

1.什麼是粘包

1.1 什麼是TCP粘包

TCP粘包就是在TCP資料傳輸過程中,因為某些原因,接收方收到讀取的資料並不是簡單的的一次請求資料,而是多個請求資料包的位元組流組裝在一起,導致多個資料粘在一起,接收端在讀取的時候不知道怎麼樣把資料分成預期的多組資料,這就是粘包。

1.2 形成原因

TCP之所以造成粘包現象是因為其傳送端和接收端的緩衝區及TCP資料流引起的。

例如nagle演算法,會將瞬間的很多小包資料拼裝稱一個大的資料,以提高的頻寬的利用率。(具體nagle演算法就不展開將)。

但即使關閉了nagle演算法,粘包依舊存在。因為這不是造成tcp粘包的根本原因。因為有緩衝區的存在,在快取區沒有打滿之前是不會傳送出去的,同時接收端也是利用快取區接收資料,在接著從快取區讀取接收的資料解析。這時有人問,如果資料量很小,總是沒有打滿緩衝區那怎麼辦,這就依賴傳送和接收端的定時器了,他們會定時的處理資料,要不這不就成了bug了。

就是因為緩衝區的存在以及tcp資料流的形式,造成了多組資料的拼接,形成了粘包,半 包問題。

1.3 如何解決

目前常用的方法是定義 起始 邊屆符+資料長度來告訴接收端一個資料包具體的長度。

不過也有定義固定長度的,不過這樣可能會造成的空白位元組的浪費以及超出長度這種不易擴充套件的方式。純邊界符的方式 怕發生實際訊息體與邊界符的碰撞,造成訊息的誤截斷。

2.netty如何解決

netty對NIO模式的TCP通訊的封裝可謂是完美。可讓人快速寫出可用的tcp通訊的服務端和客戶端,並且很簡單的解決粘包問題。

netty有提供基於分隔符和長度的編解碼器,方便開發者使用。DelimiterBaseFrameDecoder是可以使用者自定義資料分隔符來分割的,LineBaseFrameDecoder是由行尾符(\n或者\r\n)分割,速度比前者還要塊。還有基於長度的FixedLengthFrameDecoder定長的解碼器,LengthFieldBasedFrameDecoder動態長度的解碼器。這4中方式都有對應的編解碼器。

同時對於資料型別的邊界嗎,netty也支援byte,string,protobuf等,大家可以去看MessageToMessageDecoder的子類,就能發現netty提供很多編解碼的規則。

3.實戰-RPC框架的客戶和服務端實現

在自己寫KRPC時,一開始沒有把NIO的計劃提這麼早,奈何在第一版用同步IO寫客戶端,壓測時發現竟然那麼不堪,遂決定用NIO改寫,一開始覺得用Netty寫客戶端不方便(當時沒到怎麼寫),便決定用java原生的NIO來寫客戶端,寫到最後發現處理粘包特別困難,需要自己定義 特殊分界符號,然後設定長度,在客戶端和服務端解析起來特別繁雜。於是嘗試用netty寫,發現特別簡單。

bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)        .childHandler(new ChannelInitializer<
SocketChannel>
() {
@Override protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
/** * 採用可變長度來解決粘包 * 前4位儲存資料長度,所以一次呼叫最大支援的長度是65535個位元組長度,同時把前4位長度過濾掉 */ pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
/** * 計算資料長度,並放倒傳輸的資料中 */ pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
pipeline.addLast("decoder", new ByteArrayDecoder());
pipeline.addLast("encoder", new ByteArrayEncoder());
pipeline.addLast(new ServerHandler());

}
}).option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true) .childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(64,Global.getInstance().getMaxBuf(),Global.getInstance().getMaxBuf()));
複製程式碼

這就是服務端的程式碼,有沒有特別簡單,因為TCP將傳輸的資料序列化由壓縮後的資料為 位元組陣列,所以使用的自帶的ByteArray編解碼器,使用了動態長度的LengthFieldBaseFrame來解決粘包問題。就這樣解決了粘包問題。

如果想了解更具體的程式碼,可以去github下載,包含了krpc所有的程式碼。歡迎大家交流學習。

來源:https://juejin.im/post/5c04ec5bf265da61524d260d

相關文章