1. 程式人生 > >基於 Netty 的可插拔業務通訊協議的實現「2」特定業務訊息物件的設計

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

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

1 特定 Java 訊息物件通訊舉例

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

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

上述過程使用 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 方法;而對於客戶端別名設定回覆,直接使用了通用回覆類,減小了設計的複雜度。

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

訊息回覆類功能位 - 訊息類功能位 = 0x10

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

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);
    }
}