zoukankan      html  css  js  c++  java
  • netty + Protobuf (整合二)

    【正文】Protobuf 消息设计

    疯狂创客圈 死磕Netty 系列之12 【博客园 总入口

    本文说明

    本篇是 netty+Protobuf 实战的第二篇,完成一个 基于Netty + Protobuf 实战案例。

    本篇简单说明一下,实例中,设计Protobuf 消息的大致原则和思路。

    消息的大致类型

    网络通信涉及到消息的定义,不管是直接使用二进制格式,还是 xml、json等字符串格式。消息都可以大体的分为3大消息类型:

    • 请求消息

    • 应答消息

    • 命令消息

      一般情况下,每个消息还会包含一个序列号、和一个能够唯一区分消息类型的类型定义。

    原则一:使用 enum定义消息类型。

    为每个系统都定义一个 HeadType 枚举。包含系统用到的所有消息的枚举类型

    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 面试题 一网打尽

    • 疯狂创客圈 【 博客园 总入口 】


  • 相关阅读:
    JavaScript 获取来源地址
    JavaScript 调试&显示变量
    JavaScript Math对象
    JavaScript 封闭函数
    常见泛型委托
    使用BindingSource绑定数据库
    Case Reply
    RSS订阅
    ADO.NET
    泛型的优点
  • 原文地址:https://www.cnblogs.com/crazymakercircle/p/9937026.html
Copyright © 2011-2022 走看看