Netty 學習(三):通訊協議和編解碼
Netty 學習(三):通訊協議和編解碼
作者:Grey
原文地址:
無論使用 Netty 還是原生 Socket 程式設計,都可以實現自定義的通訊協議。
所謂協議就是:客戶端和服務端商量好,每一個二進位制資料包中的每一段位元組分別代表什麼含義的規則。
有了規則,在服務端和客戶端就可以通過這個設定好的規則進行二進位制和物件的轉換。
通訊協議格式可以參考如下格式
每個部分的說明如下
魔數:用來標識這個資料包是否遵循我們設計的通訊協議,類似 Java 位元組碼開頭的4位元組:0xcafebabe
版本標識:用來標識這個協議是什麼版本,用於後續協議的升級
序列化演算法:用於標識這個協議的資料包使用什麼序列化演算法,比如:JSON,XML等
指令:用於標識這個資料在收到後應該使用什麼處理邏輯。
資料長度&資料內容:不贅述
定好格式以後,
接下來我們可以約定雙方的序列化方法,這裡我們可以用 JSON 序列化/反序列化 為例,其他格式的類似。
使用 Gson 可以很方便將 JSON 字串和物件進行互轉:
private static final Gson gson = new Gson(); // 序列化 public byte[] serialize(Object object) { return gson.toJson(object).getBytes(UTF_8); } // 反序列化 public <T> T deserialize(Class<T> clazz, byte[] bytes) { return gson.fromJson(new String(bytes, UTF_8), clazz); }
實現了物件和位元組陣列的互轉以後,我們需要實現位元組陣列和 Netty 通訊載體 ByteBuf 的互轉,包括如下兩個方法
ByteBuf 編碼(資料包)
上述編碼方法需要做如下幾個事情
-
分配 ByteBuf (分配一塊記憶體區域,Netty 會直接建立一個堆外記憶體)
-
按照協議獲取資料包對應的內容
-
嚴格按照協議規定的位元組數填充到 ByteBuf 中
資料包 解碼(ByteBuf byteBuf)
上述解碼方法主要做如下幾件事情
-
校驗魔數
-
校驗版本號
-
如果嚴格按照規範傳輸的 ByteBuf,上述兩步校驗一定是通過的,可以直接跳過。
-
獲取序列化演算法,指令和資料包長度,並將資料內容轉換成位元組陣列
-
將位元組陣列轉換成對應的資料包物件。
因為不同的資料包內容有所不一樣,所以應該設定一個抽象類,由各個子類實現具體資料包的內容。
package protocol; import lombok.Data; /** * 資料包抽象類 * * @author <a href="mailto:[email protected]">Grey</a> * @date 2022/9/15 * @since */ @Data public abstract class Packet { /** * 協議版本 */ private Byte version = 1; /** * 指令,由子類實現 * * @return */ public abstract Byte getCommand(); }
對於一個具體的操作,比如登入操作,它需要的資料包需要繼承並實現這個抽象類的抽象方法。
package protocol; import lombok.Data; import static protocol.Command.LOGIN_REQUEST; /** * @author <a href="mailto:[email protected]">Grey</a> * @date 2022/9/15 * @since */ @Data public class LoginRequestPacket extends Packet { // 登入操作需要的資料內容包括如下三個 private Integer userId; private String username; private String password; @Override public Byte getCommand() { return LOGIN_REQUEST; } }
對於呼叫者來說,只需要使用 LoginRequestPacket
即可,無須關注其底層的編碼和解碼工作。虛擬碼如下
func() { LoginRequestPacket loginRequestPacket = new LoginRequestPacket(); loginRequestPacket.setVersion(((byte) 1)); loginRequestPacket.setUserId(123); loginRequestPacket.setUsername("zhangsan"); loginRequestPacket.setPassword("password"); // 編碼 ByteBuf byteBuf = 封裝好的編解碼工具類.編碼(loginRequestPacket); // 解碼 Packet decodedPacket = 封裝好的編解碼工具類.解碼(byteBuf); // 序列化成我們需要的物件 序列化和反序列化工具類.序列化(decodedPacket); }
完整程式碼見: hello-netty
本文所有圖例見: processon: Netty學習筆記
更多內容見: Netty專欄
參考資料
- 執行緒池底層原理詳解與原始碼分析
- 30分鐘掌握 Webpack
- 線性迴歸大結局(嶺(Ridge)、 Lasso迴歸原理、公式推導),你想要的這裡都有
- 【前端必會】webpack loader 到底是什麼
- 中心化決議管理——雲端分析
- HashMap底層原理及jdk1.8原始碼解讀
- 詳解JS中 call 方法的實現
- 列印 Logger 日誌時,需不需要再封裝一下工具類?
- 初識設計模式 - 代理模式
- 密碼學奇妙之旅、01 CFB密文反饋模式、AES標準、Golang程式碼
- Springboot之 Mybatis 多資料來源實現
- CAS核心思想、底層實現
- 面試突擊86:SpringBoot 事務不回滾?怎麼解決?
- 基於electron vue element構建專案模板之【打包篇】
- MiniWord .NET Word模板引擎,藉由Word模板和資料簡單、快速生成檔案。
- 認識執行緒,初始併發
- 1-VSCode搭建GD32開發環境
- 初識設計模式 - 原型模式
- 執行緒安全問題的產生條件、解決方式
- 2>&1到底是什麼意思?