在我们使用 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

最新文章

  1. gulp-less解决遇到错误停止执行task
  2. ajax返回值中有回车换行、空格的解决方法分享
  3. 设置默认python模块源
  4. js的Promise学习笔记(1)
  5. centos 7 下nginx启动脚本
  6. Changing the Color of Linux ls Command 改变Linux的ls命令显示的颜色
  7. leetcode sort List
  8. 七个结构模式之外观模式(Facade Pattern)
  9. RDIFramework.NET ━ .NET快速信息化系统开发框架 V2.7 版本发布
  10. Java传入参数个数不确定可用(Type ... values)
  11. 百部BBC经典纪录片,附地址,需要的请抱走
  12. 分享一个很好看的WPF界面
  13. Excel:您尝试打开的文件的格式与文件扩展名指定的格式不一致
  14. Qt 设置背景图片3种方法(QPalette可以做无数的事情,一旦控件指定了调色板,就要乖乖听它的话;QPainter当场绘制当然也没有问题,还有就是QSS)
  15. Java Tcp文件传输---转载
  16. 如何在jenkins的maven项目中,用mvn命令行指定findbugs的黑名单规则文件
  17. laravel 5.4中手动创建分页
  18. 总结css
  19. Angular2 之父子组件交互方式
  20. 清幽傲竹实现的kbmMWServer数据库联接失败重联(转载红鱼儿)

热门文章

  1. struts2执行流程和架构图
  2. 网络层协议、ARP攻击
  3. select后给字段起别名,where和group后不能用,但having后可以
  4. python 文件夹扫描
  5. P1251-餐巾计划问题【费用流】
  6. The art of multipropcessor programming 读书笔记-硬件基础1
  7. MySQL8.0.20下载与安装详细图文教程,mysql安装教程
  8. 11.4.3 LVS-TUN
  9. dbus客户端使用指南
  10. Linux tomcat 部署war包