protobuf netty 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相同,使得发送端可以进行事务匹配处理参考文章:

疯狂创客圈 实战计划Netty 亿级流量 高并发 IM后台 开源项目实战

Netty 源码、原理、JAVA NIO 原理

Java 面试题 一网打尽

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值