日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

Netty实战 IM即时通讯系统(八)服务端和客户端通信协议编解码

發(fā)布時間:2024/4/30 windows 63 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Netty实战 IM即时通讯系统(八)服务端和客户端通信协议编解码 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Netty實戰(zhàn) IM即時通訊系統(tǒng)(八)服務(wù)端和客戶端通信協(xié)議編解碼

零、 目錄

  • IM系統(tǒng)簡介
    • Netty 簡介
    • Netty 環(huán)境配置
    • 服務(wù)端啟動流程
    • 客戶端啟動流程
    • 實戰(zhàn): 客戶端和服務(wù)端雙向通信
    • 數(shù)據(jù)傳輸載體ByteBuf介紹
    • 客戶端與服務(wù)端通信協(xié)議編解碼
    • 實現(xiàn)客戶端登錄
    • 實現(xiàn)客戶端與服務(wù)端收發(fā)消息
    • pipeline與channelHandler
    • 構(gòu)建客戶端與服務(wù)端pipeline
    • 拆包粘包理論與解決方案
    • channelHandler的生命周期
    • 使用channelHandler的熱插拔實現(xiàn)客戶端身份校驗
    • 客戶端互聊原理與實現(xiàn)
    • 群聊的發(fā)起與通知
    • 群聊的成員管理(加入與退出,獲取成員列表)
    • 群聊消息的收發(fā)及Netty性能優(yōu)化
    • 心跳與空閑檢測
    • 總結(jié)
    • 擴展

    八、 服務(wù)端和客戶端通信協(xié)議編解碼

  • 上一小節(jié)我們學(xué)習(xí)了ByteBuf 的API , 這一小節(jié)我們拉學(xué)習(xí)如何設(shè)計并實現(xiàn)客戶端與服務(wù)端的通信協(xié)議
  • 什么是服務(wù)端與客戶端的通信協(xié)議?
  • 無論是Netty 還是原始的Socket編程 , 基于TCP通信的數(shù)據(jù)包格式均為二進制 , 協(xié)議指的就是客戶端和服務(wù)端事先商量好的 , 每一個二進制數(shù)據(jù)包中每一段字節(jié)分別表示什么含義的規(guī)則 , 如下圖的一個簡單的登錄指令
  • 這個數(shù)據(jù)包中 , 第一個字節(jié) 為1 表示這是一個登錄指令 , 接下來是用戶名和密碼 , 這兩個值以\0 分割 , 客戶端發(fā)送這段二進制數(shù)據(jù)包到服務(wù)端 , 服務(wù)端就能根據(jù)這個協(xié)議取出來用戶名密碼 , 進行登錄邏輯 , 實際的通信協(xié)議設(shè)計中 , 我們會考慮更多細節(jié) , 比這個稍微復(fù)雜一點 。
  • 那么協(xié)議設(shè)計好之后 , 客戶端和服務(wù)端之間的通信過程又是怎樣的呢?
  • 如上圖所示 , 客戶端和服務(wù)端通信:
  • 首先 , 客戶端把一個java對象按照通信協(xié)議轉(zhuǎn)換成二進制數(shù)據(jù)包
  • 然后通過網(wǎng)絡(luò) , 把這段二進制數(shù)據(jù)包發(fā)送到服務(wù)端 , 數(shù)據(jù)的傳輸過程由TCP/IP協(xié)議負責(zé)數(shù)據(jù)的傳輸 , 和我們應(yīng)用層無關(guān)
  • 服務(wù)端接收到數(shù)據(jù)之后 , 按照協(xié)議取出二進制數(shù)據(jù)包中的相應(yīng)的字段 , 包裝成java對象 , 交給應(yīng)用邏輯處理
  • 服務(wù)端處理完之后, 如果需要突出相應(yīng)給客戶端 , 那么按照相同的邏輯進行。
  • 在本系列的第一小節(jié)中我們一進列出了實現(xiàn)一個支持單聊和群聊的IM指令集合 , 我們設(shè)計協(xié)議的目的就是為了客戶端與服務(wù)端能夠識別出這些具體的指令。
  • 通信協(xié)議的設(shè)計
  • 首先第一個數(shù)是 魔數(shù) , 通常情況下為固定的幾個字節(jié) (我們這里規(guī)定4個字節(jié)) , 為什么需要這個字段 , 而且還是一個固定的數(shù)? 假設(shè)我們在服務(wù)器上開了一個端口 , 比如 80 端口 , 如果沒有這個魔數(shù) , 任何數(shù)據(jù)包傳遞到服務(wù)器 , 服務(wù)器都會根據(jù)自定義的協(xié)議進行處理 , 包括不符合自定義協(xié)議規(guī)范的數(shù)據(jù)包 。 例如: 我們直接通過http://IP:port來訪問服務(wù)器 , 服務(wù)器收到的是一個標(biāo)準(zhǔn)的Http協(xié)議數(shù)據(jù)包 , 但是他仍然會按照事先約定好的協(xié)議來處理HTTP協(xié)議 , 顯然這時會解析出錯的 , 而有了這個魔數(shù)之后 , 服務(wù)器首先取出前四個字節(jié)進行比對 , 能夠在第一時間識別出這個數(shù)據(jù)包并非是遵循自定義協(xié)議的 , 也就是說無效的數(shù)據(jù)包 , 為了安全考慮 , 可以直接關(guān)閉連接以節(jié)省資源 。 在java的二進制文件中 , 開頭的4個字節(jié)為0xcafebabe 用來表示這是一個字節(jié)碼文件 , 也是異曲同工之妙 。
  • 接下來一個字節(jié)是版本號 , 通常情況下是預(yù)留字段 , 用于協(xié)議升級的時候用到 , 有點類似TCP/IP 協(xié)議中的一個字段表示是IPV4還是IPV6 , 大多數(shù)情況下 , 這個字段是用不到的 , 不過為了協(xié)議能夠支持升級 , 我們留著 。
  • 第三部分 ,序列化算法表示如何把java對象轉(zhuǎn)換為二進制數(shù)據(jù)以及二進制數(shù)據(jù)轉(zhuǎn)換會java對象 , 比如java自帶的序列化 , json 、 hessian 等序列化方式。
  • 第四部分 表示 指令 , 關(guān)于指令的介紹 , 我們在前面已經(jīng)討論過 , 服務(wù)端或者客戶端每收到一種指令都會有相應(yīng)的處理邏輯 , 這里我們用一個字節(jié)來表示 , 最高支持256中指令 , 對于我們的IM 系統(tǒng)來說 完全夠用了
  • 接下來 的字段為數(shù)據(jù)部分的長度 , 占四個字節(jié)
  • 最后一個部分為數(shù)據(jù)部分 , 每一種指令對應(yīng)的數(shù)據(jù)是不一樣的 , 比如登錄的時候需要用戶名密碼 , 收消息的時候需要用戶標(biāo)識和具體的消息內(nèi)容
  • 通常情況下 ,這樣一套標(biāo)準(zhǔn)的協(xié)議能夠適配大多數(shù)情況下的服務(wù)端與客戶端的通信場景 , 接下來我們就來看一下 如何使用Netty 來實現(xiàn)這套協(xié)議
  • 通信協(xié)議的實現(xiàn)
  • 我們把java對象根據(jù)協(xié)議封裝成二進制數(shù)據(jù)包的過程稱為編碼 , 而把從二進制數(shù)據(jù)包中解析出就java對象的過程稱為解碼 。 在學(xué)習(xí)如何使用Netty 進行通信協(xié)議編解碼之前 , 我們先來定義一下客戶端和服務(wù)端通信的java 對象 。

  • java 對象

    /*** 數(shù)據(jù)包對象* @author outman* */ @Data abstract class Packet{/*** 協(xié)議版本* */private Byte version = 1;/*** 獲取指令* */public abstract Byte getCommand();/*** 指令集合內(nèi)部接口* */interface Command{public static final Byte LOGIN_REQUEST = 1;} }
  • 以上是通信過程中 java對象的抽象類 , 可以看到我們定義了一個版本號(默認值為1) 以及一個獲取指令的抽象方法 , 所有的指令數(shù)據(jù)包都必須實現(xiàn)這個方法 , 這樣我們就可以知道某種指令的含義

  • @Data 注解由lombok 提供 , 他會自動幫我們產(chǎn)生getter/setter 方法 , 減少大量的重復(fù)代碼 , 需要添加依賴

    <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.18</version><scope>provided</scope></dependency>
  • 接下來 , 我們一客戶登錄請求為例 , 定義登錄請求數(shù)據(jù)包 :

    @Dataclass LoginRequestPacket extends Packet{private Integer uerId ;private String userName;private String password;@Overridepublic Byte getCommand() {return Command.LOGIN_REQUEST;}}
  • 登錄請求數(shù)據(jù)包 繼承自Packet 然后定義了三個字段 , 分別是用戶ID , 用戶名 、密碼 , 這里最為重要的是覆蓋了父類的getCommand() 方法 值為常量Command.LOGIN_REQUEST
  • java對象定義完成之后 , 接下來我們就要定義一種規(guī)則 , 如何把一個java對象轉(zhuǎn)換成二進制數(shù)據(jù) , 這個規(guī)則叫做java對象的序列化

  • 序列化

  • 我們?nèi)缦露x序列化接口

    /*** 序列化接口* @author outman* */interface Serializer{/*** 序列化算法* */byte getSerializerAlgorithm();/*** java 對象轉(zhuǎn)換成二進制 (序列化) * */byte[] serialize(Object obj);/*** 二進制轉(zhuǎn)換為java對象 (反序列化)* */<T> T deSerialize(Class<T> clazz , byte[] bytes);/*** 序列化算法標(biāo)識集合接口* */interface SerializerAlgorithm{public static final byte JSON = 1;}}
  • 序列化接口有三個方法: getSerializerAlgorithm() 獲取具體的序列化算法標(biāo)識 , serialize() 將java對象轉(zhuǎn)換為字節(jié)數(shù)組 , deSerialize()將字節(jié)數(shù)組轉(zhuǎn)轉(zhuǎn)為對應(yīng)類型的java對象 , 在本小節(jié)中 , 我們使用最簡單的json序列化方式 , 使用阿里巴巴的fastJson作為序列化框架; 接口中還有一個內(nèi)部接口 , 用于讓我們定義序列化算法標(biāo)識的集合
  • fastjson 依賴

    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.54</version></dependency>
  • Json序列化實現(xiàn)類

    /*** JSON 序列化實現(xiàn)類* @author outman* */class JSONSerializer implements Serializer{@Overridepublic byte getSerializerAlgorithm() {return SerializerAlgorithm.JSON;}@Overridepublic byte[] serialize(Object obj) {return JSONObject.toJSONBytes(obj);}@Overridepublic <T> T deSerialize(Class<T> clazz, byte[] bytes) {return JSONObject.parseObject(bytes, clazz);}}
  • 我們設(shè)置 Srializer 的默認序列化方式為JSONSerializer

    /*** 序列化接口* @author outman* */interface Serializer{/*** 默認的序列化對象* */Serializer DEFAULT = new JSONSerializer();/*** 序列化算法* */byte getSerializerAlgorithm();/*** java 對象轉(zhuǎn)換成二進制 (序列化) * */byte[] serialize(Object obj);/*** 二進制轉(zhuǎn)換為java對象 (反序列化)* */<T> T deSerialize(Class<T> clazz , byte[] bytes);/*** 序列化算法標(biāo)識集合接口* */interface SerializerAlgorithm{public static final byte JSON = 1;}}
  • 這樣我們就是實現(xiàn)了序列化相關(guān)的邏輯 , 如果想要實現(xiàn)其他的序列化算法的話 , 只需要實現(xiàn)一下Serializer接口 , 然后定義一下 序列化算法標(biāo)識 就好啦。

  • 編碼: 封裝成二進制數(shù)據(jù)的過程

    /*** 數(shù)據(jù)包編解碼類* @author outman* */class PacketCodec{// 魔數(shù)private static final int MAGIC_NUMBER = 0x12345678;public ByteBuf enCode(Packet packet) {// 1. 創(chuàng)建ByteBuf 對象ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer();// 2. 序列化java對象byte[] bs = Serializer.DEFAULT.serialize(packet);// 3. 實際編碼過程byteBuf.writeInt(MAGIC_NUMBER); // 寫入魔數(shù)byteBuf.writeInt(packet.getVersion()); // 寫入?yún)f(xié)議版本號byteBuf.writeInt(Serializer.DEFAULT.getSerializerAlgorithm()); // 寫入序列化算法byteBuf.writeByte(packet.getCommand()); // 寫入指令byteBuf.writeInt(bs.length); // 寫入數(shù)據(jù)長度byteBuf.writeBytes(bs); // 寫入數(shù)據(jù)return byteBuf;}}
  • 編碼過程分為三個過程:
  • 首先我們創(chuàng)建一個ByteBuf , 這里我們調(diào)用Netty 的ByteBuf分配器來創(chuàng)建 , ioBuffrer() 方法會返回適配io讀寫相關(guān)的內(nèi)存 , 他盡可能創(chuàng)建一個直接內(nèi)存 ,直接內(nèi)存可以理解為不受jvm 對內(nèi)存法管理 , 寫到Io 緩沖區(qū)的效果更高
  • 接下來我們把java 對象序列化成二進制數(shù)據(jù)包
  • 最后我們對照本小節(jié)開頭的協(xié)議的設(shè)計以及上一小節(jié)ByeBuf 的API , 逐個往bytebuf 中寫入字段 , 及實現(xiàn)了編碼過程
  • 一端實現(xiàn)了編碼 。 Netty 會將次ByteBuf 寫到另外一端 , 另外一端拿到的也是一個ByteBuf 對象, 基于這個ByteBuf 對象 , 就可以反解出對端創(chuàng)建的java對象 , 這個過程我們稱之為解碼
  • 解碼: 解析java對象的過程

  • 還是剛剛的PacketCodec.java 中添加deCode(buf) 、 getRequestPacket(byte command) 、 getSerializer(byte serializeAlgorithm)方法

    /*** 數(shù)據(jù)包編解碼類* * @author outman*/class PacketCodec {// 魔數(shù)private static final int MAGIC_NUMBER = 0x12345678;// 指令 與 數(shù)據(jù)包 映射private final Map<Byte, Class<? super Packet>> packetTypeMap;// 序列化算法 與 序列化類 映射private final Map<Byte, Class<? super Serializer>> serializerMap;// 單例public static final PacketCodec INSTANCE = new PacketCodec();// 注冊Packet集合List<Class> packetList = Arrays.asList(new Class[] { LoginRequestPacket.class });// 注冊序列化算法集合List<Class> serializerList = Arrays.asList(new Class[] { JSONSerializer.class });/*** 初始化 指令 與 數(shù)據(jù)包 映射 序列化算法 與 序列化類 映射*/private PacketCodec() {// 初始化 指令 與 數(shù)據(jù)包 映射packetTypeMap = new HashMap<Byte, Class<? super Packet>>();packetList.forEach(clazz -> {try {Method method = clazz.getMethod("getCommand");Byte command = (Byte) method.invoke(clazz);packetTypeMap.put(command, clazz);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}});// 初始化序列化算法 與 序列化類 映射serializerMap = new HashMap<Byte, Class<? super Serializer>>();serializerList.forEach(clazz -> {try {Method method = clazz.getMethod("getSerializerAlgorithm");Byte serializerAlgorithm = (Byte) method.invoke(clazz);serializerMap.put(serializerAlgorithm, clazz);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}});}// 編碼public ByteBuf enCode(Packet packet) {// 1. 創(chuàng)建ByteBuf 對象ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer();// 2. 序列化java對象byte[] bs = Serializer.DEFAULT.serialize(packet);// 3. 實際編碼過程byteBuf.writeInt(MAGIC_NUMBER); // 寫入魔數(shù)byteBuf.writeByte(packet.getVersion()); // 寫入?yún)f(xié)議版本號byteBuf.writeByte(Serializer.DEFAULT.getSerializerAlgorithm()); // 寫入序列化算法byteBuf.writeByte(packet.getCommand()); // 寫入指令byteBuf.writeInt(bs.length); // 寫入數(shù)據(jù)長度byteBuf.writeBytes(bs); // 寫入數(shù)據(jù)return byteBuf;}// 解碼public Packet deCode(ByteBuf byteBuf) throws Exception {// 跳過 魔數(shù) 校驗byteBuf.skipBytes(4);// 跳過版本號byteBuf.skipBytes(1);// 序列化算法標(biāo)識byte serializeAlgorithm = byteBuf.readByte();// 指令標(biāo)識byte command = byteBuf.readByte();// 數(shù)據(jù)包長度int length = byteBuf.readInt();// 數(shù)據(jù)byte[] bs = new byte[length];byteBuf.readBytes(bs);// 通過序列化算法標(biāo)識獲取對應(yīng)的 序列化對象Serializer serializer = getSerializer(serializeAlgorithm);// 通過指令標(biāo)識 獲取對應(yīng)的 數(shù)據(jù)包類Packet packet = getRequestPacket(command);// 執(zhí)行解碼if (serializer != null && packet != null) {return serializer.deSerialize(packet.getClass(), bs);} else {System.out.println("沒有找到對應(yīng)的序列化對象或數(shù)據(jù)包對象");return null;}}// 通過指令獲取對應(yīng)的 數(shù)據(jù)包類 private Packet getRequestPacket(byte command) throws Exception {return (Packet) packetTypeMap.get(command).newInstance();}// 通過序列化算法標(biāo)識 獲取對應(yīng)的序列化類private Serializer getSerializer(byte serializeAlgorithm) throws Exception {return (Serializer) serializerMap.get(serializeAlgorithm).newInstance();}}
  • 解碼的流程如下
  • 我們假定deCode方法傳遞進來的ByteBuf已經(jīng)合法(后面的小節(jié)我們會實現(xiàn)校驗) 即首部4個字節(jié)是我們前面定義的魔數(shù) , 這里我們跳過
  • 這里我們暫時不關(guān)注協(xié)議版本 , 通常我們沒有遇到協(xié)議升級的時候 , 這個字段暫不處理 , 因為你會發(fā)現(xiàn) , 在大多數(shù)情況下 , 這個字段幾乎用不著 , 但是我們?nèi)匀槐A?/li>
  • 接下來我們拿到 序列化算法標(biāo)識 、 指令標(biāo)識 、 數(shù)據(jù)長度 、 數(shù)據(jù)
  • 最后我們根據(jù)拿到的數(shù)據(jù)長度取出數(shù)據(jù) , 通過指令標(biāo)識拿到對應(yīng)的java對象 , 根據(jù)序列化算法標(biāo)識 拿到序列化對象 , 將字節(jié)碼轉(zhuǎn)換為java對象
  • 完整代碼

    package com.tj.NIO_test_maven;import java.lang.reflect.Method;import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map;import com.alibaba.fastjson.JSONObject;import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import lombok.Data;/*** @author outman*/ public class Test_09_客戶端與服務(wù)端通信協(xié)議編解碼 {public static void main(String[] args) {}}/*** 數(shù)據(jù)包對象* * @author outman*/ @Data abstract class Packet {/*** 協(xié)議版本*/private Byte version = 1;/*** 獲取指令*/public abstract Byte getCommand();/*** 指令集合內(nèi)部接口*/interface Command {public static final Byte LOGIN_REQUEST = 1;} }/*** 登錄請求數(shù)據(jù)包* * @author outman*/ @Data class LoginRequestPacket extends Packet {private Integer uerId;private String userName;private String password;@Overridepublic Byte getCommand() {return Command.LOGIN_REQUEST;}}/*** 序列化接口* * @author outman*/ interface Serializer {/*** 默認的序列化對對象*/Serializer DEFAULT = new JSONSerializer();/*** 序列化算法*/byte getSerializerAlgorithm();/*** java 對象轉(zhuǎn)換成二進制 (序列化)*/byte[] serialize(Object obj);/*** 二進制轉(zhuǎn)換為java對象 (反序列化)*/<T> T deSerialize(Class<T> clazz, byte[] bytes);/*** 序列化算法標(biāo)識集合接口*/interface SerializerAlgorithm {public static final byte JSON = 1;} }/*** JSON 序列化實現(xiàn)類* * @author outman*/ class JSONSerializer implements Serializer {@Overridepublic byte getSerializerAlgorithm() {return SerializerAlgorithm.JSON;}@Overridepublic byte[] serialize(Object obj) {return JSONObject.toJSONBytes(obj);}@Overridepublic <T> T deSerialize(Class<T> clazz, byte[] bytes) {return JSONObject.parseObject(bytes, clazz);}}/*** 數(shù)據(jù)包編解碼類* * @author outman*/ class PacketCodec {// 魔數(shù)private static final int MAGIC_NUMBER = 0x12345678;// 指令 與 數(shù)據(jù)包 映射private final Map<Byte, Class<? super Packet>> packetTypeMap;// 序列化算法 與 序列化類 映射private final Map<Byte, Class<? super Serializer>> serializerMap;// 單例public static final PacketCodec INSTANCE = new PacketCodec();// 注冊Packet集合List<Class> packetList = Arrays.asList(new Class[] { LoginRequestPacket.class });// 注冊序列化算法集合List<Class> serializerList = Arrays.asList(new Class[] { JSONSerializer.class });/*** 初始化 指令 與 數(shù)據(jù)包 映射 序列化算法 與 序列化類 映射*/private PacketCodec() {// 初始化 指令 與 數(shù)據(jù)包 映射packetTypeMap = new HashMap<Byte, Class<? super Packet>>();packetList.forEach(clazz -> {try {Method method = clazz.getMethod("getCommand");Byte command = (Byte) method.invoke(clazz);packetTypeMap.put(command, clazz);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}});// 初始化序列化算法 與 序列化類 映射serializerMap = new HashMap<Byte, Class<? super Serializer>>();serializerList.forEach(clazz -> {try {Method method = clazz.getMethod("getSerializerAlgorithm");Byte serializerAlgorithm = (Byte) method.invoke(clazz);serializerMap.put(serializerAlgorithm, clazz);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}});}// 編碼public ByteBuf enCode(Packet packet) {// 1. 創(chuàng)建ByteBuf 對象ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer();// 2. 序列化java對象byte[] bs = Serializer.DEFAULT.serialize(packet);// 3. 實際編碼過程byteBuf.writeInt(MAGIC_NUMBER); // 寫入魔數(shù)byteBuf.writeByte(packet.getVersion()); // 寫入?yún)f(xié)議版本號byteBuf.writeByte(Serializer.DEFAULT.getSerializerAlgorithm()); // 寫入序列化算法byteBuf.writeByte(packet.getCommand()); // 寫入指令byteBuf.writeInt(bs.length); // 寫入數(shù)據(jù)長度byteBuf.writeBytes(bs); // 寫入數(shù)據(jù)return byteBuf;}// 解碼public Packet deCode(ByteBuf byteBuf) throws Exception {// 跳過 魔數(shù) 校驗byteBuf.skipBytes(4);// 跳過版本號byteBuf.skipBytes(1);// 序列化算法標(biāo)識byte serializeAlgorithm = byteBuf.readByte();// 指令標(biāo)識byte command = byteBuf.readByte();// 數(shù)據(jù)包長度int length = byteBuf.readInt();// 數(shù)據(jù)byte[] bs = new byte[length];byteBuf.readBytes(bs);// 通過序列化算法標(biāo)識獲取對應(yīng)的 序列化對象Serializer serializer = getSerializer(serializeAlgorithm);// 通過指令標(biāo)識 獲取對應(yīng)的 數(shù)據(jù)包類Packet packet = getRequestPacket(command);// 執(zhí)行解碼if (serializer != null && packet != null) {return serializer.deSerialize(packet.getClass(), bs);} else {System.out.println("沒有找到對應(yīng)的序列化對象或數(shù)據(jù)包對象");return null;}}// 通過指令獲取對應(yīng)的 數(shù)據(jù)包類 private Packet getRequestPacket(byte command) throws Exception {return (Packet) packetTypeMap.get(command).newInstance();}// 通過序列化算法標(biāo)識 獲取對應(yīng)的序列化類private Serializer getSerializer(byte serializeAlgorithm) throws Exception {return (Serializer) serializerMap.get(serializeAlgorithm).newInstance();}}
  • 總結(jié):
  • 通信協(xié)議書為了服務(wù)端和客戶端交互 , 雙方協(xié)商出來的滿足一定規(guī)則的二進制數(shù)據(jù)格式
  • 介紹了一種通用的通信協(xié)議的設(shè)計 , 包括魔數(shù) 、 版本號 、 序列化算法標(biāo)識 、 指令標(biāo)識 、數(shù)據(jù)長度 、 數(shù)據(jù)幾個字段 , 該協(xié)議能夠滿足大多數(shù)通信的場景
  • java對象以及序列化 , 目的就是實現(xiàn)java對象與二進制數(shù)據(jù)的互換
  • 最后我們依照我們設(shè)計的協(xié)議以及ByteBuf 的API 實現(xiàn)了通信協(xié)議 , 這個過程成為編碼過程
  • 思考:
  • 序列化和編碼都是 把java對象封裝成二進制數(shù)據(jù)的過程 , 這兩者有什么區(qū)別?
  • 序列化是把內(nèi)容變成計算機可傳輸?shù)馁Y源,而編碼則是讓程序認識這份資源。
  • 指令標(biāo)識 、 序列化算法標(biāo)識為什么不用枚舉?
  • 請問這節(jié)課自己設(shè)計的通信協(xié)議是屬于應(yīng)用層協(xié)議,和http協(xié)議是同一級別是吧?
  • 對的,這樣理解起來完全正確,只不過自定義協(xié)議屬于私有協(xié)議,http屬于共有協(xié)議
  • 使用protobuf 生成的對象的二進制要小很多,使用protobuf 可以減小數(shù)據(jù)包的大小。一個數(shù)據(jù)包無法裝下一個對象的這種情況怎么處理呢(就是一個ByteBuf 被很多個物理層數(shù)據(jù)包傳輸?shù)那闆r)?
  • 所以設(shè)計協(xié)議的時候,長度字段的長度需要考量,需要支持最大的數(shù)據(jù)包大小,這里是4個字節(jié),最大值為 2147483647,已經(jīng)完全足夠了,然后一個物理層數(shù)據(jù)包如果塞不下,會被拆成多個數(shù)據(jù)包,另外一端接受的時候把這些數(shù)據(jù)包粘合起來,可參考13小結(jié)
  • 總結(jié)

    以上是生活随笔為你收集整理的Netty实战 IM即时通讯系统(八)服务端和客户端通信协议编解码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 97人人人| 成年人小视频在线观看 | 中文字幕人妻熟女人妻a片 麻豆91视频 | 欧美日韩一级黄色片 | 国产xxxx在线观看 | 少妇理论片| 青青草色视频 | 探花av在线 | 影音先锋日韩资源 | 亚洲精品久久久狠狠狠爱 | 国模私拍一区二区 | 日本在线观看 | 色综合影视 | 91在线免费视频观看 | 黄色av片三级三级三级免费看 | 色婷婷视频在线 | 久久久久玖玖 | 精品人妻无码一区二区色欲产成人 | 欧美日本国产在线 | 欧美日韩不卡一区二区三区 | www国产精品 | 亚洲av毛片成人精品 | 又黄又爽在线观看 | 国产xxx69麻豆国语对白 | 91大尺度 | 日韩特黄毛片 | 一二三在线视频 | 亚洲天堂av电影 | 欧美国产日韩综合 | 91av国产精品 | 亚洲综合在线观看视频 | 国产免费小视频 | 黑鬼巨鞭白妞冒白浆 | 亚洲性色av | 天天爽夜夜爽 | 欧美日韩在线直播 | 红桃视频国产 | 午夜激情国产 | 男女视频久久 | 国产高清在线观看视频 | 麻豆三级在线观看 | 久久久免费看 | 最好看的中文字幕国语电影mv | 日本成人精品 | 国产一级片网址 | 亚洲精品中文在线 | 国产v亚洲 | 久久精品偷拍视频 | 少妇肥臀大白屁股高清 | 欧美精品久 | 痴汉电车在线播放 | 久久婷五月 | 精品一区二区三区人妻 | 国产一级影院 | 久久久久久日产精品 | 国产99久久九九精品无码免费 | 国产激情精品一区二区三区 | 一级高清毛片 | 久久av免费观看 | 国产女人水真多18毛片18精品 | 中文字幕一区二区三区免费 | 简单av网 | 美女被出白浆 | 九九热在线播放 | 久久影音 | 国产男男gay体育生网站 | 97在线观看| 国产传媒欧美日韩 | 国产亚洲欧美日韩精品 | 日本高清在线一区 | 用力插视频| 手机电影在线观看 | 做爰无遮挡三级 | 日日射日日操 | 日本一本高清视频 | 九九色网站 | 精品美女在线 | 插插看 | 国产精品一区麻豆 | 日本www网站 | 国产原创视频在线观看 | 欧美综合一区二区 | 国产一区二区99 | 大肉大捧一进一出好爽视频 | 台湾av在线 | 九草在线 | 欧美成人免费一级人片100 | 在线播放av网址 | 色婷婷av一区二区三区在线观看 | 久久国产加勒比精品无码 | 免费av动漫 | 日韩av网址在线观看 | 亚洲在线天堂 | 九色视频91| 久久艹这里只有精品 | 国产精品无码一区二区三区在线看 | 黄色av大片 | 日本a级片网站 | 日日射天天干 |