協議(Protocol)的概念
協議(Protocol)是指在計算機通信或網絡交互中,雙方事先約定好的規則和標準,用于規范數據如何打包、傳輸、接收和解釋。
所以簡單說就是規則,發送數據編碼的規則,接收數據解碼的規則
Dubbo中的協議
在Dubbo框架中,協議特指RPC(遠程過程調用)的通信協議,主要包括:
Dubbo協議:Dubbo默認的自定義二進制協議,基于TCP長連接,二進制頭部+序列化體,支持請求-響應模式
dubbo 協議數據包格式
dubbo協議的一個完整的數據包分為消息頭和消息體
消息頭的長度是 16 字節,在消息頭中分為5段內容
magic:占用2個字節,第一個字節值默認是 -38,第二個字節值默認是 -69
flag: 占用一個字節,位于消息頭第三位
status: 占用一個字節,位于消息頭第4為
request id: 占用8個字節,位于消息頭第5-11位,請求Id是一個 long 類型
data length: 占用4個字節,位于消息頭最后4為,長度是一個int類型
以如下消息頭為例,flag 是需要轉為 8 位的二進制數據進行解析的
status 位值為0,是因為這是一個請求報文,status 僅在響應時有效,表示響應狀態,20-OK,30-CLIENT_TIMEOUT,31-SERVER_TIMEOUT等
關于 flag 的分析
- 第0位:表示請求/響應標志(1-請求,0-響應)
- 第1位:是否為雙向通信(1-是,0-否)
- 第2位:是否為事件消息(如心跳事件)
- 第3-7位:序列化類型編號
消息頭分析完了,接下來我們根據源碼來分析
dubbo是基于Netty通信的,在dubbo服務啟動時,會初始化Netty的線程模型,設置了編解碼器類型
InternalDecoder ( 解碼器 ) 接收數據時用到
InternalEncoder ( 編碼器 ) 發送數據時用到
NettyServerHandler Netty消息處理器
在接收到數據進行解碼時,會通過
int saveReaderIndex = message.readerIndex();
記錄當前讀取位置,如果讀取到的 msg 是Codec2.DecodeResult.NEED_MORE_INPUT 時,表示需要更多數據,則會通過重置讀取數據的位置,防止出現半包問題
message.readerIndex(saveReaderIndex);
在 ExchangeCodec 中定義了消息頭的長度為16,并且讀取了消息頭到 byte[] 中
在 ExchangeCodec 中還會執行以下步驟
- 檢查magic值,檢查高位是否等于 -38,低位是否等于 -69,在dubbo中,這兩個值是固定的
- 如果可讀數據長度小于16,則返回需要更多數據
- 從消息頭中,從12位開始讀取一個int 值為消息體長度,一個int 為4字節,剛好是消息頭的最后4位
- 如果可讀數據小于消息體長度加消息頭長度,則返回需要更多數據
在 dubbo中從消息頭讀取消息長度,再讀取對應長度的消息體是解決TCP 粘包問題的關鍵所在
讀取消息頭的第2位,為 flag,在這里 flag 多處做位運算
- 從 flag 計算出協議類型 proto
- 計算消息類型是 請求類型還是響應類型
- 如果是響應類型,判斷是否是Response.OK
- 如果是請求類型,判斷是否是雙向包
- 從 proto 中可知是心跳類型還是普通消息類型
如果是請求類型時,使用 DecodeableRpcInvocation 解碼實際的消息體
如果是響應類型時,使用 DecodeableRpcResult 解碼實際的消息體
在消息的編解碼處理完成之后,解碼后的消息體會交給 NettyServerHandler 進行處理,NettyServerHandler則是dubbo接入服務的范圍了
執行流程可以參考
dubbo源碼學習1-服務提供方接入執行源碼分析