天天看點

Netty入門-通信協定編解碼

  • 設計通信協定 

Netty入門-通信協定編解碼
  1. 首先,第一個字段是魔數,通常情況下為固定的幾個位元組(我們這邊規定為4個位元組)。 為什麼需要這個字段,而且還是一個固定的數?假設我們在伺服器上開了一個端口,比如 80 端口,如果沒有這個魔數,任何資料包傳遞到伺服器,伺服器都會根據自定義協定來進行處理,包括不符合自定義協定規範的資料包。例如,我們直接通過 

    http://伺服器ip

     來通路伺服器(預設為 80 端口), 服務端收到的是一個标準的 HTTP 協定資料包,但是它仍然會按照事先約定好的協定來處理 HTTP 協定,顯然,這是會解析出錯的。而有了這個魔數之後,服務端首先取出前面四個位元組進行比對,能夠在第一時間識别出這個資料包并非是遵循自定義協定的,也就是無效資料包,為了安全考慮可以直接關閉連接配接以節省資源。在 Java 的位元組碼的二進制檔案中,開頭的 4 個位元組為

    0xcafebabe

     用來辨別這是個位元組碼檔案,亦是異曲同工之妙。
  2. 接下來一個位元組為版本号,通常情況下是預留字段,用于協定更新的時候用到,有點類似 TCP 協定中的一個字段辨別是 IPV4 協定還是 IPV6 協定,大多數情況下,這個字段是用不到的,不過為了協定能夠支援更新,我們還是先留着。
  3. 第三部分,序列化算法表示如何把 Java 對象轉換二進制資料以及二進制資料如何轉換回 Java 對象,比如 Java 自帶的序列化,json,hessian 等序列化方式。
  4. 第四部分的字段表示指令,關于指令相關的介紹,我們在前面已經讨論過,服務端或者用戶端每收到一種指令都會有相應的處理邏輯,這裡,我們用一個位元組來表示,最高支援256種指令,對于我們這個 IM 系統來說已經完全足夠了。
  5. 接下來的字段為資料部分的長度,占四個位元組。
  6. 最後一個部分為資料内容,每一種指令對應的資料是不一樣的,比如登入的時候需要使用者名密碼,收消息的時候需要使用者辨別和具體消息内容等等。

傳輸的資料示例:

Netty入門-通信協定編解碼
  •  編碼:

public ByteBuf encode() {
        // 1. 建立 ByteBuf 對象
        //傳回适配 io 讀寫相關的記憶體,它會盡可能建立一個直接記憶體,直接記憶體可以了解為不受 jvm 堆管理的記憶體空間,寫到 IO 緩沖區的效果更高。
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer();
        // 2. 序列化 Java 對象
        byte[] bytes = "傳輸的資料".getBytes();

        // 3. 實際編碼過程
        byteBuf.writeInt(1);//魔數,4個位元組
        byteBuf.writeByte(1);//版本号 1個位元組
        byteBuf.writeByte(1);//序列化算法 1個位元組
        byteBuf.writeByte(1);//指令 1個位元組
        byteBuf.writeInt(bytes.length);//資料部分的長度 4個位元組
        byteBuf.writeBytes(bytes);//資料内容

        return byteBuf;
    }
           
  • 解碼:

public void decode(ByteBuf byteBuf) {
        // 假定 decode 方法傳遞進來的 ByteBuf 已經是合法的,跳過 magic number
        byteBuf.skipBytes(4);

        // 跳過版本号
        byteBuf.skipBytes(1);

        // 序列化算法辨別
        byte serializeAlgorithm = byteBuf.readByte();

        // 指令
        byte command = byteBuf.readByte();

        // 資料包長度
        int length = byteBuf.readInt();
        System.out.println("資料包長度="+length);

        byte[] bytes = new byte[length];
        byteBuf.readBytes(bytes);

        System.out.println("資料:"+new String(bytes));
    }