疯狂创客圈,一个Java 高并发研习社群 【博客园 总入口 】
 疯狂创客圈,倾力推出:  《Netty Zookeeper Redis 高并发实战》  面试必备 + 面试必备 + 面试必备 的基础原理+实战书籍


netty+Protobuf 整合实战

疯狂创客圈 死磕Netty 亿级流量架构系列之12 【博客园 总入口

本文说明

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

要实现高并发、大流量,首先需要高传输效率的协议,Protobuf 是迄今为止最高性能之一的传输格式,我们首先将 Protobuf 和Netty整合起来。

本案例源代码

源代码下载链接: netty+protobuf (整合源代码)

What is Protobuf ?


protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#、c++、go 和python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。

Why Protobuf ?

Protobuf是由谷歌开源而来,在谷歌内部久经考验。它将数据结构以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的POJO对象和Protobuf相关的方法和属性。

特点如下:

  • 结构化数据存储格式(XML,JSON等)

  • 高效的编解码性能

  • 语言无关、平台无关、扩展性好

数据交互xml、json、protobuf格式比较
  1. json: 一般的web项目中,最流行的主要还是json。因为浏览器对于json数据支持非常好,有很多内建的函数支持。

  2. xml: 在webservice中应用最为广泛,但是相比于json,它的数据更加冗余,因为需要成对的闭合标签。json使用了键值对的方式,不仅压缩了一定的数据空间,同时也具有可读性。

  3. protobuf:是后起之秀,是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为profobuf是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。

相对于其它protobuf更具有优势
  1. 序列化后体积相比Json和XML很小,适合网络传输

  2. 支持跨平台多语言

  3. 消息格式升级和兼容性还不错

  4. 序列化反序列化速度很快,快于Json的处理速速

结论: 在一个需要大量的数据传输的场景中,如果数据量很大,那么选择protobuf可以明显的减少数据量,减少网络IO,从而减少网络传输所消耗的时间。

因而,对于打造一款高性能的通讯服务器来说,protobuf 传输格式,是最佳的解决方案。

windows 下安装 protoc

1,去这里 https://github.com/google/protobuf/releases

下载对应的protoc,本实例使用的 zip文件是老版本: protoc-2.6.1-win32.zip (本人对老版本比较属性,大家可以换成最新版本) 此工具在源代码包中已经有,可以直接解压缩源码包,直接使用

2,下好之后解压就行,然后把bin里面的 protoc.exe 加入到环境变量

3、或者,把protoc.exe拷贝到C:\Windows\System32

实战第1步:proto文件的建立

前面讲了那么多,都是一些知识铺垫,和前期的准备。

整合protobuf 的第一步,是准备一个消息的协议文件。 协议文件的后缀名称为 .proto , 该文件的定义我们需要传输的协议。实例如下:

//定义protobuf的包名称空间
option java_package = "com.crazymakercircle.chat.common.bean.msg";
// 消息体名称
option java_outer_classname = "ProtoMsg";
//.....

/*聊天消息*/
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:其他
string content = 6; //消息内容
string url = 7; //多媒体地址
string property = 8; //附加属性
string from_nick = 9; //发送者昵称
optional string json = 10; //附加的json串
}

说明:

  • 协议文件中,主要定义了最终生成的Java 代码 对应的包的名称、类的名称。分别使用 java_package、 java_outer_classname 来指定。

  • 协议文件中,每个具体的协议message对应于一个最终的Java类,协议的字段对应到类的属性。

    实际上生成的Java代码,远远不止这些。具体请参见源码包。

关于的.proto文件的格式,请大家参考 史上最简明的proto语法教程

关于的.proto消息的规则,请大家参考 史上最简明的proto消息规则

实战第2步:生成 proto 消息 Java代码

创建好.proto文件之后,就需要按照好了对应版本的 protoc.exe工具protoc.exe工具是生成Java文件的工具软件。 安装的方法,前面已经讲了。

这里需要提示一下版本。Java 的maven 配置文件中 proto 包的版本,和 .proto文件的版本, 以及生成java 代码的protoc.exe的版本,三者需要一致。

下面开始生成 消息的 Java代码。 需要用到下面的指令:


protoc.exe --java_out=输出的Java文件路径名称   .proto文件路径名称

例如:

protoc.exe --java_out=./src/main/java/ ./proto/ProtoMsg.proto

输入完之后,回车即可在目标目录看到已经生成好的Java文件,然后将该文件放到项目中该文件指定的路径下即可。

本案例的工程中,以及给大家准备好了.bat windows 的命令文件,在 .bat 目录 下执行.bat 文件即可。 .bat 文件如下:

d:
cd D:\\crazymakercircleJava\nettydemo\chatcommon
protoc.exe --java_out=./src/main/java/ ./proto/ProtoMsg.proto

使用的时候,注意调整为实际的目录。

加上对protobuf 的maven依赖

修改maven 的pom.xml文件,加上对protobuf 的依赖,代码如下:

<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>

实战第3步:构建 ProtoMsg.Message 消息

生成代码后,如果需要构建对应的消息,需要取得Java消息类型的 Builder 实例,在设置了Builder 实例的字段属性值,然后执行 Builder 实例的build() 方法。

嵌套的消息,可以通过顶层消息的 buildPartial() 取得基础部分的 Builder实例 ,然后再设置内嵌消息属性,最后执行build() 方法。

比如: mb.buildPartial().toBuilder().setMessageRequest(cb).build();

具体如下面的例子所示:

/**
* 基础 Builder
*/
private static class BaseBuilder
{
private User user;
protected ProtoMsg.HeadType type;
private long seqId;

public BaseBuilder(ProtoMsg.HeadType type,User user)
{
this.type = type;
this.user=user;
}
/**
* 构建消息 基础部分
*/
public ProtoMsg.Message buildPartial()
{
seqId = genSeqId();

ProtoMsg.Message.Builder mb = ProtoMsg.Message.newBuilder()
.setType(type)
.setSequence(seqId)
.setSessionId(user.getSessionId());
return mb.buildPartial();
}

}


/**
* 聊天消息Builder
*/
private static class ChatMsgBuilder extends BaseBuilder
{
//...
public ProtoMsg.Message build()
{
//基础部分
ProtoMsg.Message message = buildPartial();
//内嵌部分
ProtoMsg.MessageRequest.Builder cb
= ProtoMsg.MessageRequest.newBuilder();
//组合起来,然后构建
return message.toBuilder().setMessageRequest(cb).build();
}
}

实战第4步:编码器

在发出ProtoMsg.Message 消息前,还需要对二进制消息进一步封装。

使用2字节消息长度+Message(二进制数据)+(2字节CRC校验(可选))

其中2字节的内容,只包含Message的长度,不包含自身和CRC的长度。如果需要也可以包含,当要记得通信双方必须一致。

编码器如下:


public class ProtobufEncoder extends MessageToByteEncoder<ProtoMsg.Message> {

@Override
protected void encode(ChannelHandlerContext ctx, ProtoMsg.Message msg, ByteBuf out)
throws Exception {
byte[] bytes = msg.toByteArray();// 将对象转换为byte
int length = bytes.length;// 读取消息的长度

ByteBuf buf = Unpooled.buffer(2 + length);
buf.writeShort(length);// 先将消息长度写入,也就是消息头
buf.writeBytes(bytes);// 消息体中包含我们要发送的数据
out.writeBytes(buf);

} }

实战第五步 解码器

与编码器的操作相反,去掉头部的两个字节,然后转换成 ProtoMsg.Message 消息

/**
* 解码器
*
*/
public class ProtobufDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out) throws Exception {
// 标记一下当前的readIndex的位置
in.markReaderIndex();
// 判断包头长度
if (in.readableBytes() < 2) {// 不够包头
return;
}

// 读取传送过来的消息的长度。
int length = in.readUnsignedShort();

// 长度如果小于0
if (length < 0) {// 非法数据,关闭连接
ctx.close();
}

if (length > in.readableBytes()) {// 读到的消息体长度如果小于传送过来的消息长度
// 重置读取位置
in.resetReaderIndex();
return;
}

ByteBuf frame = Unpooled.buffer(length);
in.readBytes(frame);
try {
byte[] inByte = frame.array();

// 字节转成对象
ProtoMsg.Message msg = ProtoMsg.Message.parseFrom(inByte); if (msg != null) {
// 获取业务消息头
out.add(msg);
}
} catch (Exception e) {
LOG.info(ctx.channel().remoteAddress() + ",decode failed.", e);
}

}
}

实战第六步 解码器

将编码器和解码器,加入pipeline中,代码如下:

// 设置通道初始化
bootstrap.handler(
new ChannelInitializer<SocketChannel>()
{
public void initChannel(SocketChannel ch) throws Exception
{
ch.pipeline().addLast(new ProtobufDecoder());
ch.pipeline().addLast(new ProtobufEncoder());
ch.pipeline().addLast(chatClientHandler);

}
}
);

这一块,很简单。

写在最后

终于大功告成。

为了方便大家理解 netty 和 protobuf 整合的过程, 实例进行了裁剪,仅仅剩下了 上面这块非常很重要的部分。

如果需要真正的理解上面的内容,建议大家一定要去跑实例。


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

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

  • Java 面试题 一网打尽

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


netty+Protobuf (整合一)的更多相关文章

  1. netty + Protobuf (整合二)

    [正文]Protobuf 消息设计 疯狂创客圈 死磕Netty 系列之12 [博客园 总入口 ] 本文说明 本篇是 netty+Protobuf 实战的第二篇,完成一个 基于Netty + Proto ...

  2. 转: 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端

    from: http://ybak.iteye.com/blog/1853335 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端 游戏服 ...

  3. Spring - Netty (整合)

      写在前面  ​ 大家好,我是作者尼恩.目前和几个小伙伴一起,组织了一个高并发的实战社群[疯狂创客圈].正在开始 高并发.亿级流程的 IM 聊天程序 学习和实战,此文是:   疯狂创客圈 Java ...

  4. Netty 粘包/半包原理与拆包实战

    Java NIO 粘包 拆包 (实战) - 史上最全解读 - 疯狂创客圈 - 博客园 https://www.cnblogs.com/crazymakercircle/p/9941658.html 本 ...

  5. Netty聊天室(2):从0开始实战100w级流量应用

    目录 客户端 Client 登录和响应处理 写在前面 客户端的会话管理 客户端的逻辑构成 连接服务器与Session 的创建 Session和 channel 相互绑定 AttributeMap接口的 ...

  6. protobuf + maven 爬坑记

    疯狂创客圈 死磕Netty 亿级流量架构系列之20 [博客园 总入口 ] 本文说明 本篇是 netty+Protobuf 整合实战的 第一篇,完成一个 基于Netty + Protobuf 实战案例. ...

  7. Netty聊天室-源码

    目录 Netty聊天室 源码工程 写在前面 [百万级流量 聊天室实战]: [分布式 聊天室] [Spring +Netty]: [Netty 原理] 死磕 系列 [提升篇]: [内力大增篇]: 疯狂创 ...

  8. Netty聊天器(实战一):从0开始实战100w级流量应用

    Java 聊天程序(百万级流量实战一):系统介绍 疯狂创客圈 Java 分布式聊天室[ 亿级流量]实战系列之14 [博客园 总入口 ] 源码IDEA工程获取链接:Java 聊天室 实战 源码 写在前面 ...

  9. Netty 粘包 拆包 | 史上最全解读

    Netty 粘包/半包原理与拆包实战(史上最全) 疯狂创客圈 Java 聊天程序[ 亿级流量]实战系列之13 [博客园 总入口 ] 本文的源码工程:Netty 粘包/半包原理与拆包实战 源码 本实例是 ...

随机推荐

  1. ado:SqlDataAdapter,dataset 与SqlDataReader的用法一

    原文发布时间为:2008-08-01 -- 来源于本人的百度文章 [由搬家工具导入] using System;using System.Data;using System.Configuration ...

  2. elementui table 分页 和 tabel 前加序列号

    记录下来备忘 结构如下 Report.vue <template> <div> <home-header></home-header> <div ...

  3. CSS 盒模型、解决方案、BFC 原理讲解--摘抄

    PS:内容比较基础,目的只是覆盖面试知识点,大佬可以 history.back(-1) W3C 标准盒模型 & IE 怪异盒模型 页面上显示的每个元素(包括内联元素)都可以看作一个盒子,即盒模 ...

  4. 37深入理解C指针之---结构体与指针

    一.结构体与指针 1.结构体的高级初始化.结构体的销毁.结构体池的应用 2.特征: 1).为了避免含有指针成员的结构体指针的初始化复杂操作,将所有初始化动作使用函数封装: 2).封装函数主要实现内存的 ...

  5. python常用模块1

    一. 什么是模块: 常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀. 但其实import加载的模块分为四个通用类别: 1 使用python编写的代码 ...

  6. Windows Phone 8 与 windows 8 开发技术概览

    目前来说Windows phone 8的开发者 大家都是走战斗在在技术朋友,相信大家在做Windows Phone 8开发的同时也在关注Windows 8,我相信很多开发者一定是在 Windows 8 ...

  7. Powershell简介及其编程访问

    Powershell是下一代的命令行外壳程序,较之于它的前身(cmd.exe),它的功能更加强大,也更加易用.最根本的区别在于它是基于对象的操作(基于.NET Framework),而不是基于字符串的 ...

  8. 公司内部技术分享之Vue.js和前端工程化

    今天主要的核心话题是Vue.js和前端工程化.我将结合我这两年多的工作学习经历来谈谈这个,主要侧重点是前端工程化,Vue.js侧重点相对前端工程化,比重不是特别大. Vue.js Vue.js和Rea ...

  9. SQL盲注工具BBQSQL

    SQL盲注工具BBQSQL   SQL注入是将SQL命令插入到表单.域名或者页面请求的内容中.在进行注入的时候,渗透测试人员可以根据网站反馈的信息,判断注入操作的结果,以决定后续操作.如果网站不反馈具 ...

  10. [转] 32位 PL/SQL Develope r如何连接64位的Oracle 图解

    原文地址:LINK 由于硬件技术的不断更新,Win7系统逐渐成为主流,而且计算机内存逐渐增大,为了充 分的利用内存资源(因为32为系统最多只能用到3G左右的内存),提高系统性能,很多人开始使用Win7 ...