netty + Protobuf (整合二)

【正文】Protobuf 消息設計

瘋狂創客圈 死磕Netty 系列之12 【博客園 總入口

本文說明

本篇是 netty+Protobuf 實戰的第二篇,完成一個 基於Netty + Protobuf 實戰案例。html

本篇簡單說明一下,實例中,設計Protobuf 消息的大體原則和思路。java

消息的大體類型

網絡通訊涉及到消息的定義,不論是直接使用二進制格式,仍是 xml、json等字符串格式。消息均可以大致的分爲3大消息類型:android

  • 請求消息ios

  • 應答消息web

  • 命令消息面試

    通常狀況下,每一個消息還會包含一個序列號、和一個可以惟一區分消息類型的類型定義。 json

原則一:使用 enum定義消息類型。

爲每一個系統都定義一個 HeadType 枚舉。包含系統用到的全部消息的枚舉類型windows

 
 
 
 
enum HeadType
{
  Login_Request = 1;//登錄請求
  Login_Response = 2;//登陸響應
  Logout_Request = 3;//退出請求
  Logout_Response = 4;
  Keepalive_Request = 5;//心跳請求ping;
  Keepalive_Response = 6;
  Message_Request = 7;//消息請求;
  Message_Response = 8;//消息回執;
  Message_Notification = 9;//通知消息
}

原則二: 一個 protobuf message 對應一類消息

會爲每一個具備消息體的消息定義一個對應的protobuf message。服務器

例如Login_Request會有一個對應LoginRequest消息。網絡


/*登陸信息*/
// LoginRequest對應的HeadType爲Login_Request
// 消息名稱去掉下劃線,更加符合Java 的類名規範
message LoginRequest{
    required string uid = 1;        // 用戶惟一id
    required string deviceId = 2;     // 設備ID
    required string token = 3;       // 用戶token
    optional uint32 platform = 4;      //客戶端平臺 windows、mac、android、ios、web
    optional string app_version = 5;    // APP版本號
}

原則三:應答消息須要成功標記和應答序號

對於應答消息,並不是老是成功的,所以在應答消息中還會包含另外2個字段。

  • 一個用於描述應答是否成功,一個用於描述失敗時的字符串信息。

  • 對於有多個應答的消息來講,可能會包含是否爲最後一個應答消息的標識——應答的序號(相似與網絡數據包被分包之後,協議要合併時,須要知道分片在包中的具體位置)。

    所以Response看起來是這樣:

 
 
 
 
/*聊天響應*/
message MessageResponse
{
    required bool result = 1; //true表示發送成功,false表示發送失敗
    required uint32 code = 2;   //錯誤碼
    required string info = 3;   //錯誤描述
    required uint32 expose = 4; //錯誤描述是否提示給用戶:1 提示;0 不提示
    required bool last_block = 5;
    required fixed32 block_index = 6;
}

原則四:編解碼從頂層消息開始

最後我會定義一個大消息,把全部的消息類型,所有封裝在一塊兒,讓後在通訊的時候都從頂層消息開始編解碼。大消息看起來想下面這樣。。

 
 
 
 
/*頂層消息*/
//頂層消息是一種嵌套消息,嵌套了各類類型消息
//內部的消息類型,所有使用optional字段
//根據消息類型 type的值,最多隻有一個有效
message Message
{
 required HeadType type = 1; //消息類型
 required fixed32 sequence = 2;//消息系列號
 fixed32  session_id = 3;
 optional LoginRequest loginRequest = 4;
 optional LoginResponse loginResponse = 5;
 optional MessageRequest messageRequest = 6;
 optional MessageResponse messageResponse = 7;
 optional MessageNotification notification = 8;
}

原則五:TCP 消息須要進行二進制包裝

用於UDP的時候比較簡單,由於每一個數據包就是一個獨立的Message消息,能夠直接解碼,或者編碼後直接發送。

可是若是是使用於TCP的時候,因爲涉及到粘包、拆包等處理,並且Message消息裏面也沒有包含長度相關的字段(很差處理),所以把Message編碼後的消息嵌入另一個二進制消息中。

使用4字節消息長度+Message(二進制數據)+(2字節CRC校驗(可選))

其中4字節的內容,只包含Message的長度,不包含自身和CRC的長度。若是須要也能夠包含,當要記得通訊雙方必須一致。

協議接口文件完整 實例

下面是一個 爲瘋狂創客圈 100W*100級 分佈式 IM項目定義 google protobuf 的協議接口文件


  
  
  
  
//定義protobuf的包名稱空間
​
option java_package = "com.crazymakercircle.chat.common.bean.msg";
​
// 消息體名稱
option java_outer_classname = "ProtoMsg";
​
​
enum HeadType
{
  LOGIN_REQUEST = 1;//登錄請求
  LOGIN_RESPONSE = 2;//登陸響應
  LOGOUT_REQUEST = 3;//退出請求
  LOGOUT_RESPONSE = 4;
  KEEPALIVE_REQUEST = 5;//心跳請求PING;
  KEEPALIVE_RESPONSE = 6;
  MESSAGE_REQUEST = 7;//消息請求;
  MESSAGE_RESPONSE = 8;//消息回執;
  MESSAGE_NOTIFICATION = 9;//通知消息
}
​
/*登陸信息*/
// LoginRequest對應的HeadType爲Login_Request
// 消息名稱去掉下劃線,更加符合Java 的類名規範
message LoginRequest{
    required string uid = 1;        // 用戶惟一id
    required string deviceId = 2;     // 設備ID
    required string token = 3;       // 用戶token
    optional uint32 platform = 4;      //客戶端平臺 windows、mac、android、ios、web
    optional string app_version = 5;    // APP版本號
}
​
//token說明: 帳號服務器登陸時生成的Token
​
/*登陸響應*/
message LoginResponse{
    required bool  result = 1; //true 表示成功,false表示失敗
    required uint32 code = 2;   //錯誤碼
    required string info = 3;   //錯誤描述
    required uint32 expose = 4; //錯誤描述是否提示給用戶:1 提示;0 不提示
    required string session_id = 5;     //sessionId
}
​
​
​
/*聊天消息*/
message MessageRequest{
     uint64 msg_id = 1;     //消息id
     string from = 2;       //發送方uId
     string to = 3;         //接收方uId
     uint64 time = 4;       //時間戳(單位:毫秒)
     required uint32 msg_type = 5;  //消息類型  1:純文本  2:音頻 3:視頻 4:地理位置 5:其餘
   required string session_id = 6;      //sessionId
   string content = 7;  //消息內容
     string url = 8;        //多媒體地址
     string property = 9;   //附加屬性
     string from_nick = 10; //發送者暱稱
     optional string json = 11;     //附加的json串
}
​
/*聊天響應*/
message MessageResponse
{
    required bool result = 1; //true表示發送成功,false表示發送失敗
    required uint32 code = 2;   //錯誤碼
    required string info = 3;   //錯誤描述
    required uint32 expose = 4; //錯誤描述是否提示給用戶:1 提示;0 不提示
    required bool last_block = 5;
    required fixed32 block_index = 6;
}
​
/*通知消息*/
message MessageNotification
{
 required uint32 msg_type = 1;  //通知類型 1 上線 2 下線 ...
 required bytes sender = 2;
 required string json = 3;
 required string timestamp = 4;
}
​
/*頂層消息*/
//頂層消息是一種嵌套消息,嵌套了各類類型消息
//內部的消息類型,所有使用optional字段
//根據消息類型 type的值,最多隻有一個有效
message Message
{
 required HeadType type = 1; //消息類型
 required uint64   sequence = 2;//消息系列號
 required fixed32  session_id = 3;
 optional LoginRequest loginRequest = 4;
 optional LoginResponse loginResponse = 5;
 optional MessageRequest messageRequest = 6;
 optional MessageResponse messageResponse = 7;
 optional MessageNotification notification = 8;
}
​
​
// sequence 消息系列號
// 主要用於Request和Response,Response的值必須和Request相同,使得發送端能夠進行事務匹配處理

參考文章:

個人Protobuf消息設計原則



瘋狂創客圈 實戰計劃
  • Netty 億級流量 高併發 IM後臺 開源項目實戰

  • Netty 源碼、原理、JAVA NIO 原理

  • Java 面試題 一網打盡

  • 瘋狂創客圈 【 博客園 總入口 】

相關文章
相關標籤/搜索