摘要:基本消息對象的設計消息對象的設計主要由兩部分組成特定數據幀對應的特定消息對象。該類包含上節數據幀主幀及子幀的所有公共信息,僅僅未包含子幀中的數據體信息,該需求由基本消息對象的子類實現。
開發工程中,有一個常見的需求:服務端程序和多個客戶端程序通過 TCP 協議進行通信,通信雙方需通信的消息種類眾多,并且客戶端的數量可能有數萬個。為此,雙方需要約定盡可能豐富、靈活的數據幀「數據包」協議,方便后續業務功能的設計。
本文設計了一種通信協議,為壓縮數據量,該協議的數據幀以二進制方式進行傳輸并識別,即其基本單位為字節,必要時將部分字節流手動轉化為可讀文本。通過設定功能位來實現豐富的通信消息類型,并且采用注冊的方式,可方便擴展新的業務消息類型,可靈活地增刪通信消息對象。采用 Netty 框架保證高并發場景下程序的性能。
系統整體設計框圖如下:
1. 通信數據幀協議的設計 1.1 數據幀主幀的幀格式首先給出通用的數據幀格式如下,一個數據幀主幀由:幀識別位、幀功能位、設備號、數據長度、數據體等 5 部分組成。「其實最通用的數據幀只有幀識別位,根據幀識別位確定幀類型,從而確定其余四個部分,本文中幀識別位固定,幀格式即固定了」
幀識別位:確定數據幀的開始,亦確定本幀的幀類型。
幀功能位:確定該幀所傳送的消息類型,特定的幀功能位對應特定的數據體。
設備號:設備的識別號,服務端據此識別不同的客戶端。
數據長度:數據體所占用的字節數。
數據體:根據幀功能位,所確定的需傳輸的具體的消息。
1.2 數據幀子幀的幀格式數據幀除數據體以外的部分稱為幀頭,考慮這樣一種需求,如果某幀所要傳輸的數據體部分內容很少,導致一個幀的大部分容量均被幀頭占據,導致有效數據的占比很小,這就產生了巨大的浪費,舉例如下:
如一個開鎖幀,只需傳輸一個開鎖信號即可,消息的接收方、消息類型均體現在了幀頭中,數據部分只需要 0 個或 1 個字節即可。
客戶端需要向服務器發送自己的當前狀態信息,該狀態信息可能也只需要 1 個字節左右。
由于如上實際的需求,如果增大了每一幀的有效數據的占比,整個通信鏈路的數據量會明顯減少,IO 負擔也會因此減輕,所以據此繼續對幀協議進行設計。
如上圖,對數據幀主幀中的「數據體」部分進行進一步拆分,數據幀主幀的數據體部分由子幀組成,子幀由:子幀功能位、數據長度、數據體等 3 部分組成。
子幀功能位:確定該子幀所傳送的消息類型,總而言之,主幀、子幀功能位共同確定了該子幀的消息類型。
數據長度:數據體所占用的字節數。
數據體:根據子幀功能位,所確定的需傳輸的具體的消息。
1.3 數據幀的幀格式總覽完整的幀格式如下圖所示,數據幀主幀的數據體部分完全由子幀組成,通信雙方通信時,可以往一個主幀中添加多個子幀,從而可以極大提高鏈路的使用效率。
2 數據幀處理模塊的實現數據幀已進行了如上精心設計,將設計的數據幀通過程序實現并投入實際使用才是最終目的。
2.1 數據幀處理的基本方法以服務端的工作為例來進行說明。服務端程序監聽指定端口,客戶端通過 TCP 協議向服務器發送二進制數據消息,服務端接收到二進制數據并進行處理,此處采用責任鏈模式,Netty 框架內建了方便的基于責任鏈模式的消息處理方法:
第一個處理器將捕獲的數據截取為一個一個協議約定的數據幀并送入下層處理器,如果捕獲的二進制數據未符合協議約定的格式,則可以直接丟棄。「此處未考慮半包、粘包等場景」
第二個處理器捕獲到約定的數據幀,則著手對不同類型數據幀進行解析,解析為不同類型的 Java 消息對象,并將反序列化成功并驗證成功的 Java 對象送入下層處理器。如果上述過程失敗,可以認為客戶端設計不合理,導致出現無效消息,直接丟棄該對象,也可以繼續通知服務端或客戶端該異常情況。
第三個處理器捕獲到正確的 Java 消息對象,則可以直接送入上層 Java 模塊進行處理,此處可根據不同的對象類型送入不同的上層處理模塊,或者在此處進行其他的工作「比如消息日志記錄工作等」。
2.2 基本 Java 消息對象的設計Java 消息對象的設計主要由兩部分組成:
特定數據幀對應的特定 Java 消息對象。
特定 Java 消息對象對應的特定的該消息對象編解碼器。
以下是基本 Java 消息對象:
public abstract class BaseMsg implements Cloneable { private final BaseMsgCodec msgCodec; private int groupId; private int deviceId; private int resendTimes = 0; protected BaseMsg(BaseMsgCodec msgCodec, int groupId, int deviceId) { this.msgCodec = msgCodec; this.groupId = groupId; this.deviceId = deviceId; } /** * 獲取該消息對象的細節描述 * * @return 該消息對象的細節描述 */ public String msgDetailToString() { return msgCodec.getDetail() + "[majorMsgId=" + Integer.toHexString(msgCodec.getMajorMsgId()).toUpperCase() + ", subMsgId=" + Integer.toHexString(msgCodec.getSubMsgId()).toUpperCase() + ", groupId=" + groupId + ", deviceId=" + deviceId + "]"; } /** * 重發該消息對象的記錄信息更新 */ public void doResend() { resendTimes++; } }
由上述代碼可知,每個消息對象均包含該對象對應編解碼器的引用,方便獲取該消息對象的擴展信息,或者方便將該消息對象重新序列化為數據幀。該類包含上節數據幀主幀及子幀的所有公共信息,僅僅未包含子幀中的數據體信息,該需求由基本 Java 消息對象的子類實現。
該類由 abstract 修飾,是抽象類,無法直接實例化,具體的工作由該類的子類完成,即由具體的真正業務相關的 Java 消息對象完成。
以下為 Java 消息對象的基本編解碼器:
/** * 單個消息對象「幀」的編解碼器 */ public abstract class BaseMsgCodec implements SubFramecoder, SubFramedecoder { private final int majorMsgId; private final int subMsgId; private final String detail; protected BaseMsgCodec(int majorMsgId, int subMsgId, String detail) { this.majorMsgId = majorMsgId; this.subMsgId = subMsgId; this.detail = detail; } public String getDetail() { return detail; } public int getMajorMsgId() { return majorMsgId; } public int getSubMsgId() { return subMsgId; } }
由上述代碼可知,特定 Java 消息對象的編解碼器由數據幀的主幀、子幀功能位共同決定,這樣確保了消息編解碼器的規范,避免消息過多時的混亂。
Java 編解碼器實現了如下兩個接口,表明編解碼器可將 Java 消息對象編碼為數據幀,或將數據幀解碼為指定的 Java 消息對象:
public interface SubFramecoder { /** * 將 Java 消息對象編碼為數據幀 * * @param msg 消息對象 * @param buffer TCP 數據幀的容器 * @return 生成的 TCP 數據幀的 ByteBuf */ ByteBuf code(BaseMsg msg, ByteBuf buffer); } public interface SubFramedecoder { /** * 將數據幀解碼為指定的 Java 消息對象 * * @param groupId 設備組 ID * @param deviceId 設備 ID * @param data 幀數據 * @return 特定的 Java 消息對象 */ BaseMsg decode(int groupId, int deviceId, byte[] data); }相關項目參考「GitHub 項目基礎框架開源」
Java & Vue.js「集群設備管理云平臺『后端部分』」
基于 Vue.js 2.0 & Element 2.0 的集群設備管理云平臺
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68607.html
摘要:而實際兩者之間的通信使用的是基于的自定義二進制數據幀,對象與數據幀之間需進行轉換。該類實現了編碼解碼方法,故可對消息對象進行編碼或對數據幀進行解碼。該類的靜態方法可通過指定功能消息對象生成相應的回復對象。 本文為該系列的第二篇文章,設計需求為:服務端程序和眾多客戶端程序通過 TCP 協議進行通信,通信雙方需通信的消息種類眾多。上一篇文章詳細描述了該通信協議的二進制數據幀格式以及基本 J...
摘要:本文仍以該實例為例,探討該自定義通信協議的具體工作流程,以及如何以注冊的形式靈活插拔通信消息對象。進行二進制數據幀的解碼操作時,數據幀中已包含了消息的功能位,據此可獲取相應的編解碼器,而后可以對該數據幀進行解析,生成相應的消息對象。 本文為該系列的第三篇文章,設計需求為:服務端程序和眾多客戶端程序通過 TCP 協議進行通信,通信雙方需通信的消息種類眾多。上一篇文章以一個具體的需求為例,...
摘要:比特幣和以太幣屬于一類區塊鏈,我們將其歸類為公共無許可的區塊鏈技術。例如,在單個企業中部署時,或由受信任的權威機構運作,完全拜占庭容錯的共識可能被認為是不必要的,并且對性能和吞吐量造成過度的拖累。 介紹 一般而言,區塊鏈是一個不可變的交易分類賬,維護在一個分布式對等節點網絡中。這些節點通過應用已經由共識協議驗證的交易來維護分類帳的副本,該交易被分組為包括將每個塊綁定到前一個塊的散列的塊...
摘要:當用戶注銷或退出時,釋放連接,清空對象中的登錄狀態。聊天管理模塊系統的核心模塊,這部分主要使用框架實現,功能包括信息文件的單條和多條發送,也支持表情發送。描述讀取完連接的消息后,對消息進行處理。 0.前言 最近一段時間在學習Netty網絡框架,又趁著計算機網絡的課程設計,決定以Netty為核心,以WebSocket為應用層通信協議做一個互聯網聊天系統,整體而言就像微信網頁版一樣,但考慮...
閱讀 1309·2021-11-15 11:37
閱讀 2564·2021-09-22 10:56
閱讀 3391·2021-09-06 15:11
閱讀 801·2021-08-31 09:45
閱讀 2897·2021-07-28 11:16
閱讀 1806·2019-08-30 15:44
閱讀 477·2019-08-30 13:22
閱讀 3344·2019-08-30 13:18