netty中使用protobuf实现多协议的消息
在我们使用 netty 的过程中,有时候为了高效的传输数据,经常使用 protobuf 进行数据的传输,netty默认情况下为我们实现的 protobuf 的编解码,但是默认的只能实现单个对象的编解码,但是我们在使用 netty 的过程中,可能需要传输的对象有各种各样的,那么该如何实现对protobuf多协议的解码呢?
在 protobuf 中有一种类型的字段叫做 oneof , 被 oneof 声明的字段就类似于可选字段,在同一时刻只有一个字段有值,并且它们会共享内存。
有了上述基础知识,我们来实现一个简单的功能。
需求:
客户端在连接上服务器端后,每隔 1s 向服务器端发送一个 protobuf 类型的对象(比如登录报文、创建任务报文、删除任务报文等等),服务器端接收到这个对象并打印出来。
protobuf文件的编写:
在protobuf 文件中,我们申明一个 枚举类型的字段,用来标识当前发送的 protobuf 对象的类型,比如是登录报文、创建任务报文还是别的,然后在 oneof 字段中,申明所有可能需要传递的 报文实体。
一、protobuf-java jar包的引入
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.6.1</version>
</dependency>
二、proto 文件的编写
注意:
1、定义的枚举是为了标识当前发送的是什么类型的消息
2、需要发送的多个消息统一放入到 oneof 中进行申明
3、到时候给 netty 编解码的时候就编解码 TaskProtocol 对象
三、使用 protoc 命令根据 .proto 文件生成 对应的 java 代码
四、netty服务器端的编写
/**
* netty protobuf server
*
* @author huan.fu
* @date 2019/2/15 - 11:54
*/
@Slf4j
public class NettyProtobufServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup parentGroup = new NioEventLoopGroup(1);
EventLoopGroup childGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
// 连接超时
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000)
.handler(new LoggingHandler(LogLevel.TRACE))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline()
.addLast(new ProtobufVarint32FrameDecoder())
.addLast(new ProtobufDecoder(TaskProtobufWrapper.TaskProtocol.getDefaultInstance()))
.addLast(new ProtobufVarint32LengthFieldPrepender())
.addLast(new ProtobufEncoder())
.addLast(new ServerProtobufHandler());
}
});
// 绑定端口,同步等待成功
ChannelFuture future = bootstrap.bind(9090).sync();
log.info("server start in port:[{}]", 9090);
// 等待服务端链路关闭后,main线程退出
future.channel().closeFuture().sync();
// 关闭线程池资源
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
注意:
1、注意一下 netty 是如何使用那些编解码器来编解码 protobuf 的。
五、服务器端接收到客户端发送过来的消息的处理
/**
* 服务器端接收到客户端发送的请求,然后随机给客户端返回一个对象
*
* @author huan.fu
* @date 2019/2/15 - 14:26
*/
@Slf4j
public class ServerProtobufHandler extends SimpleChannelInboundHandler<TaskProtobufWrapper.TaskProtocol> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TaskProtobufWrapper.TaskProtocol taskProtocol) {
switch (taskProtocol.getPackType()) {
case LOGIN:
log.info("接收到一个登录类型的pack:[{}]", taskProtocol.getLoginPack().getUsername() + " : " + taskProtocol.getLoginPack().getPassword());
break;
case CREATE_TASK:
log.info("接收到一个创建任务类型的pack:[{}]", taskProtocol.getCreateTaskPack().getTaskId() + " : " + taskProtocol.getCreateTaskPack().getTaskName());
break;
case DELETE_TASK:
log.info("接收到一个删除任务类型的pack:[{}]", Arrays.toString(taskProtocol.getDeleteTaskPack().getTaskIdList().toArray()));
break;
default:
log.error("接收到一个未知类型的pack:[{}]", taskProtocol.getPackType());
break;
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
log.error("发生异常", cause);
}
}
注意:
1、服务器端根据 packType 字段来判断客户端发送的是什么类型的消息
六、netty 客户端的编写
/**
* netty protobuf client
*
* @author huan.fu
* @date 2019/2/15 - 11:54
*/
@Slf4j
public class NettyProtobufClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline()
.addLast(new ProtobufVarint32FrameDecoder())
.addLast(new ProtobufDecoder(TaskProtobufWrapper.TaskProtocol.getDefaultInstance()))
.addLast(new ProtobufVarint32LengthFieldPrepender())
.addLast(new ProtobufEncoder())
.addLast(new ClientProtobufHandler());
}
});
ChannelFuture future = bootstrap.connect("127.0.0.1", 9090).sync();
log.info("client connect server.");
future.channel().closeFuture().sync();
group.shutdownGracefully();
}
}
七、客户端连接到服务器端时的处理
/**
* 客户端连接到服务器端后,每隔1s发送一个报文到服务器端
*
* @author huan.fu
* @date 2019/2/15 - 14:26
*/
@Slf4j
public class ClientProtobufHandler extends ChannelInboundHandlerAdapter {
private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
private AtomicInteger atomicInteger = new AtomicInteger(1);
@Override
public void channelActive(ChannelHandlerContext ctx) {
executor.scheduleAtFixedRate(() -> {
// 产生的pack类型
int packType = new Random().nextInt(3);
switch (TaskProtobufWrapper.PackType.forNumber(packType)) {
case LOGIN:
TaskProtobufWrapper.LoginPack loginPack = TaskProtobufWrapper.LoginPack.newBuilder().setUsername("张三[" + atomicInteger.getAndIncrement() + "]").setPassword("123456").build();
ctx.writeAndFlush(TaskProtobufWrapper.TaskProtocol.newBuilder().setPackType(TaskProtobufWrapper.PackType.LOGIN).setLoginPack(loginPack).build());
break;
case CREATE_TASK:
TaskProtobufWrapper.CreateTaskPack createTaskPack = TaskProtobufWrapper.CreateTaskPack.newBuilder().setCreateTime(System.currentTimeMillis()).setTaskId("100" + atomicInteger.get()).setTaskName("任务编号" + atomicInteger.get()).build();
ctx.writeAndFlush(TaskProtobufWrapper.TaskProtocol.newBuilder().setPackType(TaskProtobufWrapper.PackType.CREATE_TASK).setCreateTaskPack(createTaskPack).build());
break;
case DELETE_TASK:
TaskProtobufWrapper.DeleteTaskPack deleteTaskPack = TaskProtobufWrapper.DeleteTaskPack.newBuilder().addTaskId("1001").addTaskId("1002").build();
ctx.writeAndFlush(TaskProtobufWrapper.TaskProtocol.newBuilder().setPackType(TaskProtobufWrapper.PackType.DELETE_TASK).setDeleteTaskPack(deleteTaskPack).build());
break;
default:
log.error("产生一个未知的包类型:[{}]", packType);
break;
}
}, 0, 1, TimeUnit.SECONDS);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
log.error("发生异常", cause);
}
}
注意:
1、客户端在连接服务器端时每隔1s发送不同的消息到服务器端
八、运行结果
九、完整代码
完成代码如下:https://gitee.com/huan1993/netty-study/tree/master/src/main/java/com/huan/netty/protobuf
netty中使用protobuf实现多协议的消息的更多相关文章
- netty系列之:在netty中使用protobuf协议
目录 简介 定义protobuf 定义handler 设置ChannelPipeline 构建client和server端并运行 总结 简介 netty中有很多适配不同协议的编码工具,对于流行的goo ...
- Netty 中的消息解析和编解码器
本篇内容主要梳理一下 Netty 中编解码器的逻辑和编解码器在 Netty 整个链路中的位置. 前面我们在分析 ChannelPipeline 的时候说到入站和出站事件的处理都在 pipeline 中 ...
- netty系列之:protobuf在UDP协议中的使用
目录 简介 UDP在netty中的表示 DatagramPacketEncoder DatagramPacketDecoder 总结 简介 netty中提供的protobuf编码解码器可以让我们直接在 ...
- Netty(五)序列化protobuf在netty中的使用
protobuf是google序列化的工具,主要是把数据序列化成二进制的数据来传输用的.它主要优点如下: 1.性能好,效率高: 2.跨语言(java自带的序列化,不能跨语言) protobuf参考文档 ...
- SuperSocket与Netty之实现protobuf协议,包括服务端和客户端
今天准备给大家介绍一个c#服务器框架(SuperSocket)和一个c#客户端框架(SuperSocket.ClientEngine).这两个框架的作者是园区里面的江大渔. 首先感谢他的无私开源贡献. ...
- Netty中如何序列化数据
JDK提供了ObjectOutputStream和ObjectInputStream,用于通过网络对POJO的基本数据类型和图进行序列化和反序列化.该API并不复杂,而且可以被应用于任何实现了java ...
- SpringBoot整合Netty并使用Protobuf进行数据传输(附工程)
前言 本篇文章主要介绍的是SpringBoot整合Netty以及使用Protobuf进行数据传输的相关内容.Protobuf会简单的介绍下用法,至于Netty在之前的文章中已经简单的介绍过了,这里就不 ...
- netty 的 Google protobuf 开发
根据上一篇博文 Google Protobuf 使用 Java 版 netty 集成 protobuf 的方法非常简单.代码如下: server package protobuf.server.imp ...
- Netty(五):Netty中如何序列化数据
JDK提供了ObjectOutputStream和ObjectInputStream,用于通过网络对POJO的基本数据类型和图进行序列化和反序列化.该API并不复杂,而且可以被应用于任何实现了java ...
随机推荐
- Selenium自动化实现web自动化-1
框架搭建 基于maven+jdk8+junit5+seleium 构建 <dependencies> <dependency> <groupId>org.junit ...
- angularjs实现购物清单
HTML: 1:要定义ng-app,在html上定义ng-app="App"; 2:在body上定义ng-controller="ToDoCtrl" 3: &l ...
- weblogic漏洞分析之CVE-2017-3248 & CVE-2018-2628
CVE-2017-3248 & CVE-2018-2628 后面的漏洞就是2017-3248的绕过而已,所以poc都一样,只是使用的payload不同 本机开启JRMP服务端 ->利用T ...
- Docker系列(19)- 数据卷之Dockerfile
初识Dockerfile Dockerfile就是用来构建docker镜像的构建文件!命令脚本! 通过这个脚本生成镜像,镜像是一层一层的,脚本与一个个的命令,每个命令都是一层! # 创建一个docke ...
- Linux系列(15) - man
简介 查看命令帮助,是个帮助命令 格式 man [选项] 命令 选项 -f:相当于 whatis 命令,查询一个命令执行什么功能,这个命令是什么级别的,并将查询结果打印到终端 -k:相当于 aprop ...
- Django边学边记--状态保持(cookie和session)
Cookie 概念: Cookie,也叫Cookies,指某些网站为了辨别用户身份.进行session跟踪而储存在用户本地终端上的数据(通常经过加密),好比会员卡或餐票. 特点: Cookie是由服务 ...
- sonar扫面代码总体流程
- jmeter加密解密(解密篇)
上一篇已经讲解了公钥加密,这篇讲解公钥解密.解密比较简单,直接操作吧. 需求是:接口中的请求体的部分参数需要先加密再请求,返回的结果中部分字段需解密. 1.在请求下新建beanshell后置处理程序, ...
- 鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 百篇博客分析OpenHarmony源码 | v23.02
百篇博客系列篇.本篇为: v23.xx 鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...
- P6085-[JSOI2013]吃货JYY【状压dp,欧拉回路】
正题 题目链接:https://www.luogu.com.cn/problem/P6085 题目大意 \(n\)个点的一张无向图,有\(k\)条必走边,\(m\)条其他边,求从\(1\)出发经过必走 ...