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專欄