netty + Protobuf (整合二)
文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 语雀版 | 总目录 码云版| 总目录 博客园版 为您奉上珍贵的学习资源 :
-
免费赠送 :《尼恩Java面试宝典》持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
-
免费赠送 经典图书:《Java高并发核心编程(卷1)》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
-
免费赠送 经典图书:《Java高并发核心编程(卷2)》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
-
免费赠送 经典图书:《Netty Zookeeper Redis 高并发实战》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
-
免费赠送 经典图书:《SpringCloud Nginx高并发核心编程》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
-
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取
推荐:入大厂 、做架构、大力提升Java 内功 的 精彩博文
入大厂 、做架构、大力提升Java 内功 必备的精彩博文 | 秋招涨薪1W + 必备的精彩博文 |
---|---|
1:Redis 分布式锁 (图解-秒懂-史上最全) | 2:Zookeeper 分布式锁 (图解-秒懂-史上最全) |
3: Redis与MySQL双写一致性如何保证? (面试必备) | 4: 面试必备:秒杀超卖 解决方案 (史上最全) |
5:面试必备之:Reactor模式 | 6: 10分钟看懂, Java NIO 底层原理 |
7:TCP/IP(图解+秒懂+史上最全) | 8:Feign原理 (图解) |
9:DNS图解(秒懂 + 史上最全 + 高薪必备) | 10:CDN图解(秒懂 + 史上最全 + 高薪必备) |
11: 分布式事务( 图解 + 史上最全 + 吐血推荐 ) | 12:限流:计数器、漏桶、令牌桶 三大算法的原理与实战(图解+史上最全) |
13:架构必看:12306抢票系统亿级流量架构 (图解+秒懂+史上最全) |
14:seata AT模式实战(图解+秒懂+史上最全) |
15:seata 源码解读(图解+秒懂+史上最全) | 16:seata TCC模式实战(图解+秒懂+史上最全) |
SpringCloud 微服务 精彩博文 | |
---|---|
nacos 实战(史上最全) | sentinel (史上最全+入门教程) |
SpringCloud gateway (史上最全) | 分库分表sharding-jdbc底层原理与实操(史上最全,5W字长文,吐血推荐) |
推荐:尼恩Java面试宝典(持续更新 + 史上最全 + 面试必备)具体详情,请点击此链接
尼恩Java面试宝典,32个最新pdf,含2000多页,不断更新、持续迭代 具体详情,请点击此链接
【正文】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 面试题 一网打尽
疯狂创客圈 【 博客园 总入口 】