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 面试题 一网打尽
疯狂创客圈 【 博客园 总入口 】
netty + Protobuf (整合二)的更多相关文章
- netty+Protobuf (整合一)
netty+Protobuf 整合实战 疯狂创客圈 死磕Netty 亿级流量架构系列之12 [博客园 总入口 ] 本文说明 本篇是 netty+Protobuf 整合实战的 第一篇,完成一个 基于Ne ...
- Netty Reator(二)Scalable IO in Java
Netty Reator(二)Scalable IO in Java Netty 系列目录 (https://www.cnblogs.com/binarylei/p/10117436.html) Do ...
- 转: 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端
from: http://ybak.iteye.com/blog/1853335 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端 游戏服 ...
- Netty入门(二):Channel
前言 Netty系列索引: 1.Netty入门(一):ByteBuf 2.Netty入门(二):Channel 在Netty框架中,Channel是其中之一的核心概念,是Netty网络通信的主体,由它 ...
- Netty 学习(二):服务端与客户端通信
Netty 学习(二):服务端与客户端通信 作者: Grey 原文地址: 博客园:Netty 学习(二):服务端与客户端通信 CSDN:Netty 学习(二):服务端与客户端通信 说明 Netty 中 ...
- Spring - Netty (整合)
写在前面 大家好,我是作者尼恩.目前和几个小伙伴一起,组织了一个高并发的实战社群[疯狂创客圈].正在开始 高并发.亿级流程的 IM 聊天程序 学习和实战,此文是: 疯狂创客圈 Java ...
- SSM整合(二):Spring4与Mybatis3整合
上一节测试好了Mybatis3,接下来整合Spring4! 一.添加spring上下文配置 在src/main/resources/目录下的spring新建spring上下文配置文件applicati ...
- Spring+Quartz 整合二:调度管理与定时任务分离
新的应用场景:很多时候,我们常常会遇到需要动态的添加或修改任务,而spring中所提供的定时任务组件却只能够通过修改xml中trigger的配置才能控制定时任务的时间以及任务的启用或停止,这在带给我们 ...
- Struts2.3.34+Hibernate 4.x+Spring4.x 整合二部曲之上部曲
1 导入jar包 可以复制jar包或maven导入,本文最后会给出github地址 2 导入log4j.properties文件 og4j.appender.stdout=org.apache.log ...
随机推荐
- 点滴积累【C#】---验证码,ajax提交
效果: 思路: 借用ashx文件创建四位验证,首先生成四位随机数字.然后创建画布,再将创建好的验证码存入session,然后前台进行button按钮将文本框中的值进行ajax请求到后台,和sessio ...
- Powershell分支条件
Where-Object 进行条件判断很方便,如果在判断后执行很多代码可以使用IF-ELSEIF-ELSE语句.语句模板: If(条件满足){如果条件满足就执行代码}Else{如果条件不满足}条件判断 ...
- ext异常,ExceptionReturn
package cn.edu.hbcf.common.vo; import java.io.PrintWriter; import java.io.StringWriter; /** * Ext 异常 ...
- 大数据处理-Trie树
大数据处理--Trie树 1.1.什么是Trie树 Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被 ...
- python文章的抓取
# coding = utf-8import urllibimport sysimport urllibimport jsonimport socketimport timesys.path.appe ...
- 蓝桥杯 第三届C/C++预赛真题(10) 取球游戏(博弈)
今盒子里有n个小球,A.B两人轮流从盒中取球,每个人都可以看到另一个人取了多少个,也可以看到盒中还剩下多少个,并且两人都很聪明,不会做出错误的判断. 我们约定: 每个人从盒子中取出的球的数目必须是:1 ...
- Java逍遥游记读书笔记<三>
异常处理 如何判断一个方法中可能抛出异常 该方法中出现throw语句 该方法调用了其他已经带throws子句的方法. 如果方法中可能抛出异常,有两种处理方法: 1.若当前方法有能力处理异常,则用Try ...
- 性能测试:压测中TPS上不去的几种原因分析(就是思路要说清楚)
转https://www.cnblogs.com/imyalost/p/8309468.html 先来解释下什么叫TPS: TPS(Transaction Per Second):每秒事务数,指服务器 ...
- Linux严格区分大小写
虚拟机上安装了MySQL,使用rpm -qa | grep mysql查询时却未找到安装的mysql,后面才发现Linux严格区分大小写,正确的查询命令应该为rpm -qa | grep MySQL, ...
- 使用 Visual Studio 2015 编译 QT 工程
简单进行一下几步就可以了 1.下载源代码 qt-everywhere-opensource-src-5.6.0-alpha.7z .解压到 D:\ToolKits\5.6.0\src 目录下2.网站 ...