基於 Netty 的可插拔業務通訊協議的實現「2」特定業務消息對象的設計

本文爲該系列的第二篇文章,設計需求爲:服務端程序和衆多客戶端程序經過 TCP 協議進行通訊,通訊雙方需通訊的消息種類衆多。上一篇文章詳細描述了該通訊協議的二進制數據幀格式以及基本 Java 消息類,假設通訊雙方「服務端、客戶端」均由 Netty 框架構建而成,雙方在程序內部使用 Java 消息對象,通訊雙方信息交互採用的是自定義二進制幀格式,本文經過一個具體實例,探討指定的 Java 消息對象與其相應的二進制數據幀相互轉換的方法。java

1 特定 Java 消息對象通訊舉例

本小節以一個具體的需求爲例,講述該自定義通訊協議的工做流程。該需求爲:對某一個特定的客戶端進行命名。該需求的具體工做流程描述以下:數據庫

服務端需主動向指定的客戶端發送消息,對客戶端設置指定的名稱,客戶端接到指定的消息並驗證合法後,需向服務端反饋消息接受成功的確認回覆,服務器接收到該回復後,便可認爲對客戶端進行命名的消息發送成功而且名字設置成功,若服務端在指定的時間內未收到回覆,需進行重發或者向上層「如管理員或數據庫」反饋該客戶端的異常。bash

上述過程使用 UML 序列圖演示以下:服務器

UML 序列圖演示客戶端別名的設置

由上圖能夠直觀地看出:管理員對服務器的操做以及服務器對管理員的反饋均爲動做,Server 與 Client 之間的通訊以 Java 的視角均經過 Java 消息對象,共需兩個對象:客戶端別名設置對象、客戶端別名設置回覆對象。而實際二者之間的通訊使用的是基於 TCP 的自定義二進制數據幀,對象與數據幀之間需進行轉換。架構

2 該任務所需 Java 消息類的設計

上小節所述過程須要兩個 Java 消息類,以下所示:框架

  1. 客戶端別名設置類
/** * 「消息對象」客戶端別名設置 */
public class MsgDeviceName extends BaseMsg {

    private final String name;

    public MsgDeviceName(BaseMsgCodec msgCodec, int groupId, int deviceId, String name) {
        super(msgCodec, groupId, deviceId);
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String msgDetailToString() {
        return super.msgDetailToString() + "別名:" + name;
    }
}
複製代碼
  1. 客戶端別名設置回覆類「直接使用通用回覆類」
/** * 「消息對象」通用消息回覆 */
public class MsgReplyNormal extends BaseMsg {

    public MsgReplyNormal(BaseMsgCodec msgCodec, int groupId, int deviceId) {
        super(msgCodec, groupId, deviceId);
    }

    @Override
    public String msgDetailToString() {
        return super.msgDetailToString();
    }
}
複製代碼

客戶端別名設置類相比於基礎消息類,覆寫了消息細節描述方法,優化調試日誌的使用體驗。主要改變是,僅僅增長了客戶端別名的引用及其 Get 方法;而對於客戶端別名設置回覆,直接使用了通用回覆類,減少了設計的複雜度。ide

該自定義幀協議有一個設計要點:每個功能性消息類均有相對應的特定回覆類。從功能位的角度來看,該兩種類的主幀功能位之間存在以下關係:優化

消息回覆類功能位 - 消息類功能位 = 0x10
複製代碼

即兩類的功能位數值之差以十六進制表示爲 0x10。據此設計功能性 Java 消息類後,不須要專門設計對應的回覆類,系統會自行使用該通用回覆類進行工做。this

3 該任務所需消息類編解碼器的設計

編碼器可將 Java 消息對象編碼爲數據幀,解碼器可講數據幀解碼爲指定的 Java 消息對象,上節所述的兩種消息類均須要相對應的編解碼器,以下所示:編碼

3.1 客戶端別名設置編解碼器類

該類相比於基礎類,新增了編解碼器的靜態工廠方法,手動傳入功能位及功能文字描述,進而生成包含這些參數的編解碼器。如此設計,使得全部消息的功能位和文字描述均可以統一管理,下降維護成本。

該類實現了編碼、解碼方法,故可對消息對象進行編碼或對數據幀進行解碼。該類的實現以下所示:

/** * 「消息對象編解碼器」客戶端別名設置 */
public class MsgCodecDeviceName extends BaseMsgCodec {

    private static MsgCodecDeviceName msgCodec = null;

    public MsgCodecDeviceName (int majorMsgId, int subMsgId, String detail) {
        super(majorMsgId, subMsgId, detail);
        msgCodec = this;
    }

    public static MsgDeviceName create(int groupId, int deviceId, String name) {
        return new MsgDeviceName(msgCodec, groupId, deviceId, name);
    }

    @Override
    public ByteBuf code(BaseMsg msg, ByteBuf buffer) {
        MsgDeviceName message = (MsgDeviceName) msg;
        buffer.writeByte(message.getSubMsgId());
        byte[] data = KyToArrayUtil.stringToArray(message.getName());
        buffer.writeShort(data.length);
        buffer.writeBytes(data);
        return buffer;
    }

    @Override
    public MsgDeviceName decode(int groupId, int deviceId, byte[] data) {
        String name = KyToArrayUtil.arrayToString(data);
        return create(groupId, deviceId, name);
    }
}
複製代碼

3.2 通用回覆編解碼器類

該類相比於基礎類,新增了編解碼器的靜態工廠方法,實現了編解碼器,理由與上小節相同。該類的 createByBaseMsg(BaseMsg) 靜態方法可經過指定功能消息對象生成相應的回覆對象。該類的實現以下所示:

/** * 「消息對象編解碼器」通用消息回覆 */
public class MsgCodecReplyNormal extends BaseMsgCodec {

    private static MsgCodecReplyNormal msgCodec = null;

    public MsgCodecReplyNormal(int majorMsgId, int subMsgId, String detail) {
        super(majorMsgId, subMsgId, detail);
        msgCodec = this;
    }

    /** * 根據收到的消息對象,建立新的通用消息回覆對象, * * @param msg 收到的消息對象 * @return 新的通用消息回覆對象 */
    public static MsgReplyNormal createByBaseMsg(BaseMsg msg) {
        BaseMsgCodec msgCodec = MsgCodecToolkit.getMsgCodec(msg.getMajorMsgId() + 0x10, msg.getSubMsgId());
        if (msgCodec == null) {
            return null;
        }
        return new MsgReplyNormal(msgCodec, msg.getGroupId(), msg.getDeviceId());
    }

    /** * 建立新的通用消息回覆對象 * * @param groupId 組號 * @param deviceId 設備號 * @return 生成的通用消息回覆對象 */
    private MsgReplyNormal create(int groupId, int deviceId) {
        return new MsgReplyNormal(this, groupId, deviceId);
    }

    @Override
    public ByteBuf code(BaseMsg msg, ByteBuf buffer) {
        MsgReplyNormal message = (MsgReplyNormal) msg;
        buffer.writeByte(message.getSubMsgId());
        buffer.writeShort(0);
        return buffer;
    }

    @Override
    public MsgReplyNormal decode(int groupId, int deviceId, byte[] data) {
        return create(groupId, deviceId);
    }
}
複製代碼
相關文章
相關標籤/搜索