netty自定義解碼器 decoder

最近由于一個項目需要和單片機(jī)通信,和硬件工程師溝通好之后石蔗,大致確定協(xié)議為 :

消息頭部 + 消息長度 + 設(shè)備號 + 命令 + data + crc16
由于netty自帶的decoder有些不滿足這個格式畅形,所以自定義了一個decoder。

代碼如下

/**
 * 消息格式為  消息頭部(1字節(jié)) + 消息長度(2字節(jié)) + 設(shè)備號(12字節(jié)) + 命令(2字節(jié)) + data(n字節(jié)) + crc16(2字節(jié))
 *
 * @author watermelon
 * @time 2020/5/21
 */
public class SmartHomeDecoder extends ByteToMessageDecoder implements SmartHomeCodeC {

    private final Logger LOG = LoggerFactory.getLogger(SmartHomeDecoder.class);
    /**
     * ByteBuf 超過這個值之后棍厌,會清除已讀區(qū)域
     * 默認(rèn)不清除
     */
    private int clearReadMaxLength;

    /**
     * 默認(rèn)構(gòu)造器,ByteBuf 可能會無限擴(kuò)容
     * ByteBuf 超過1024之后敬肚,會清除已讀區(qū)域
     */
    public SmartHomeDecoder() {
        this(0);
    }

    /**
     * 
     * ByteBuf 超過 clearReadMaxLength 之后束析,會清除已讀區(qū)域
     *
     * @param clearReadMaxLength
     */
    public SmartHomeDecoder(int clearReadMaxLength) {
        this(clearReadMaxLength);
    }

    private SmartHomeDecoder(int clearReadMaxLength) {
        this.clearReadMaxLength = clearReadMaxLength;
    }


    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = this.decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

    /**
     * 解碼消息
     *
     * @param ctx
     * @param in
     * @throws Exception
     */
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        clearRead(in);
        //消息小于接收的最小長度
        if (in.readableBytes() < MSG_MIN_LENGTH) {
            return null;
        }
        //記錄消息頭部位置
        int beginIndex;
        while (true) {
            //獲取消息頭部位置
            beginIndex = in.readerIndex();
            //讀到消息頭部的時候,跳出循環(huán)
            byte b = in.readByte();
            if (b == HEADER) {
                break;
            }
            //如果讀完了所有的數(shù)據(jù) 都沒有獲取到 消息頭部 則判定所有消息為無效消息弄慰,直接放棄掉
            if (in.readableBytes() == 0) {
                return null;
            }
        }

        //消息長度
        if (in.readableBytes() < MSG_LENGTH_LENGTH) {
            //消息長度不夠,還原readerIndex到消息頭部的位置
            in.readerIndex(beginIndex);
            return null;
        }

        //獲取 消息長度           
        //長度 共兩個字節(jié) 所以將第一個左移8位
        int length1 = in.readByte();
        length1 = length1 << 8;
        int length2 = in.readByte();
        //最終的長度
        length1 = length1 + length2;
        //判斷數(shù)據(jù)包是否完整
        if (in.readableBytes() < length1) {
            //還原readerIndex到消息頭部的位置
            in.readerIndex(beginIndex);
            return null;
        }
        //讀取數(shù)據(jù)
        byte[] data = new byte[length1];
        in.readBytes(data);
        //所有的數(shù)據(jù)
        byte[] data1 = ConvertUtil.byteSplit(data, 0, data.length - CSC2_LENGTH);
        //獲取數(shù)據(jù)對應(yīng)的 crc2 校驗碼
        byte[] crc= ConvertUtil.crc(data1);
        //獲取傳過來來的校驗碼
        byte[] crc2 = ConvertUtil.byteSplit(data, data.length - CSC2_LENGTH, CSC2_LENGTH);
        //比較,如果校驗不通過陆爽,就忽略這次消息
        if (!Arrays.equals(crc, crc2)) {
            LOG.debug("crc2校驗不通過");
            return null;
        }
        //將得到的data 根據(jù)約定 轉(zhuǎn)換為實體
        return new MessagePush().setReceiveEntity(new MessageDistributor(data1).distribute());
    }

    /**
     * 清除 0 - readIndex 的數(shù)據(jù)扳缕,以免  ByteBuf 過大
     * 如果每一次消息最后,都帶有一段解析不了的臟消息驴剔,或者有一段小于{@link #MSG_MIN_LENGTH} 的消息,這樣每次都會有未讀完的消息粥庄, 就可能導(dǎo)致 ByteBuf 無限擴(kuò)容
     *
     * @param in
     */
    private void clearRead(ByteBuf in) {
        if (clearReadMaxLength > 0 && in.writerIndex() > clearReadMaxLength) {
            LOG.debug("byteBuf中留存的數(shù)據(jù)太大,自動清除已讀數(shù)據(jù)");
            in.discardReadBytes();
        }
    }
}

整個解碼主要是檢索頭部飒赃,然后根據(jù)頭部之后的長度去讀取消息。

這里主要是處理了一下當(dāng)出現(xiàn)了粘包問題炒事,消息不完整的時候蔫慧,指針要回到頭部,等待下一次讀取完整消息姑躲。

以及合理的設(shè)置一個 clearReadMaxLength ,當(dāng)緩沖區(qū)過大時卖怜,清除掉已經(jīng)讀取的數(shù)據(jù)阐枣。

鏈接:https://wmelon.cn/348.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奄抽,一起剝皮案震驚了整個濱河市甩鳄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌档泽,老刑警劉巖揖赴,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異甜熔,居然都是意外死亡突倍,警方通過查閱死者的電腦和手機(jī)盆昙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秕磷,“玉大人炼团,你說我怎么就攤上這事∥林ィ” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵晤郑,是天一觀的道長贸宏。 經(jīng)常有香客問我,道長吭练,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任赐稽,我火速辦了婚禮,結(jié)果婚禮上晰绎,老公的妹妹穿的比我還像新娘括丁。我一直安慰自己,他們只是感情好史飞,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布构资。 她就那樣靜靜地躺著,像睡著了一般吐绵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唉窃,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天纹笼,我揣著相機(jī)與錄音,去河邊找鬼廷痘。 笑死,一個胖子當(dāng)著我的面吹牛元暴,可吹牛的內(nèi)容都是我干的鳞陨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼厦滤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了享怀?” 一聲冷哼從身側(cè)響起趟咆,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤梅屉,失蹤者是張志新(化名)和其女友劉穎鳞贷,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惰聂,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡咱筛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年搓幌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迅箩。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡溉愁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饲趋,到底是詐尸還是另有隱情拐揭,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布篙贸,位于F島的核電站投队,受9級特大地震影響枫疆,放射性物質(zhì)發(fā)生泄漏爵川。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一息楔、第九天 我趴在偏房一處隱蔽的房頂上張望寝贡。 院中可真熱鬧,春花似錦圃泡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辆亏,卻和暖如春风秤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扮叨。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工缤弦, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人彻磁。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓碍沐,卻偏偏與公主長得像狸捅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子累提,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355