Netty结合Protobuf进行编解码的方法


Posted in Java/Android onJune 26, 2021

 

 一般在使用netty时,数据传输的时候都会选择对传输的数据进行编解码,编码后的数据变小, 有利于在有限的带宽下传输更多的数据。

由于java本身序列化的缺点较多(无法跨语言,序列化后的码流太大,序列化的性能太低等),业界主流的编解码框架主要有如下三个:

  1. Google的Protobuf
  2. Facebook的Thrift
  3. JBoss的Marshalling

今天我们简单介绍一下Netty结合google的Protobuf框架进行数据的编解码。

1. 什么是Protobuf?

Protobuf全称是Google Protocol Buffers, 它是谷歌公司开源的一个序列化框架。

它将数据结构以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的POJO对象和Protobuf相关的方法和属性。

它的特点如下:

  1. 结构化数据存储格式
  2. 高效的编解码性能
  3. 语言无关、平台无关、扩展性好
  4. 官方支持多个语言(java,c++,python,c#等)

2. 下载安装

Protobuf已经托管在github上,可以在release页面下载,本案例使用的是v3.6(要下载后缀为-win32.zip的)。

下载后,将压缩包进行解压。这里主要用到bin目录下的protoc.exe。

3. 定义好proto文件

SubscribeReq.proto

// 区分不同的protobuf版本,必须有
syntax = "proto2";
 
package netty;
// 生成的目标类的包路径
option java_package = "cn.ddlover.nettystudy.protobuf";
// 生成的目标类的名字
option java_outer_classname = "SubscribeReqProto";
 
message SubscribeReq{
    required int32 subReqID = 1;
    required string userName = 2;
    required string productName = 3;
    repeated string address = 4;
}

SubscribeResp.proto

syntax = "proto2";
package netty;
 
option java_package = "cn.ddlover.nettystudy.protobuf";
option java_outer_classname = "SubscribeRespProto";
 
message SubscribeResp{
    required int32 subReqID = 1;
    required int32 respCode = 2;
    required string desc = 3;
}

此时项目结构如下

Netty结合Protobuf进行编解码的方法

重点关注一下两个proto文件的位置,是位于项目的根路径下

4. 分别执行以下命令

D:\xhb\protoc-3.6.1-win32\bin\protoc.exe --java_out=.\src\main\java SubscribeResp.proto 

D:\xhb\protoc-3.6.1-win32\bin\protoc.exe --java_out=.\src\main\java SubscribeReq.proto 

这里需要把protoc.exe的位置换成自己的, --java_out命令设置的是proto文件中java_package指令的父目录。

5. 此时项目结构如下

Netty结合Protobuf进行编解码的方法

下面开始代码方面的开发,

1. 先贴一波maven的配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>cn.ddlover</groupId>
    <artifactId>nettystudy</artifactId>
    <version>1.0-SNAPSHOT</version>
 
 
    <properties>
        <java.version>1.8</java.version>
    </properties>
 
    <dependencies>
        <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.33.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.6.1</version>
        </dependency>
 
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
    </dependencies>
</project>

2. 这里贴上完整代码的项目结构

Netty结合Protobuf进行编解码的方法

3. SubReqServer.java

import cn.ddlover.nettystudy.handler.SubReqServerHandler;
import cn.ddlover.nettystudy.protobuf.SubscribeReqProto;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
 
/**
 * Protobuf版本图书订购代码
 */
public class SubReqServer {
    private static final int PORT = 8080;
 
    public static void main(String[] args) {
        bind(PORT);
    }
 
    private static void bind(int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
 
        ServerBootstrap b = new ServerBootstrap();
        try {
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // 半包处理
                            socketChannel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
                            socketChannel.pipeline().addLast(new ProtobufDecoder(SubscribeReqProto.SubscribeReq.getDefaultInstance()));
                            socketChannel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
                            socketChannel.pipeline().addLast(new ProtobufEncoder());
                            socketChannel.pipeline().addLast(new SubReqServerHandler());
                        }
                    });
            ChannelFuture future = b.bind(port).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

4. SubReqServerHandler.java

import cn.ddlover.nettystudy.protobuf.SubscribeReqProto;
import cn.ddlover.nettystudy.protobuf.SubscribeRespProto;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
 
public class SubReqServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq)msg;
 
        if ("张三".equals(req.getUserName())) {
            System.out.println("Server accept clietn subscribe req : ["+req.toString()+"]");
            ctx.writeAndFlush(resp(req.getSubReqID()));
        }
    }
 
    private SubscribeRespProto.SubscribeResp resp(int subReqID) {
        SubscribeRespProto.SubscribeResp.Builder builder = SubscribeRespProto.SubscribeResp.newBuilder();
        builder.setSubReqID(subReqID);
        builder.setRespCode(0);
        builder.setDesc("netty书籍下单成功,3天后将会送到你的住处");
        return builder. build();
    }
 
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

5. SubReqClient.java

import cn.ddlover.nettystudy.handler.SubReqClientHandler;
import cn.ddlover.nettystudy.protobuf.SubscribeRespProto;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
 
public class SubReqClient {
    private static final String HOST = "localhost";
    private static final int PORT = 8080;
 
    public static void main(String[] args) {
        connect(HOST, PORT);
    }
 
    private static void connect(String host, int port) {
        EventLoopGroup group = new NioEventLoopGroup();
 
        Bootstrap b = new Bootstrap();
        try {
            b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
 
                            // 半包处理
                            socketChannel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
                            socketChannel.pipeline().addLast(new ProtobufDecoder(SubscribeRespProto.SubscribeResp.getDefaultInstance()));
                            socketChannel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
                            socketChannel.pipeline().addLast(new ProtobufEncoder());
                            socketChannel.pipeline().addLast(new SubReqClientHandler());
                        }
                    });
            ChannelFuture f = b.connect(host, port).sync();
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            group.shutdownGracefully();
        }
    }
}

6. SubReqClientHandler.java

import cn.ddlover.nettystudy.protobuf.SubscribeReqProto;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
 
import java.util.ArrayList;
import java.util.List;
 
public class SubReqClientHandler extends ChannelInboundHandlerAdapter {
 
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i =0;i<10;i++) {
            ctx.write(subReq(i));
        }
        ctx.flush();
    }
 
    private SubscribeReqProto.SubscribeReq subReq(int i) {
        SubscribeReqProto.SubscribeReq.Builder builder = SubscribeReqProto.SubscribeReq.newBuilder();
        builder.setSubReqID(i);
        builder.setUserName("张三");
        builder.setProductName("Netty Book");
        List<String> address = new ArrayList<>();
        address.add("NanJing YuHuaTai");
        address.add("BeiJing LiuLiChang");
        address.add("ShenZhen HongShuLin");
        builder.addAllAddress(address);
        return builder.build();
    }
 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Receive server response : ["+ msg +"]");
    }
 
 
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

代码部分到此就结束了,然后分别运行SubReqServerSubReqClient, 就可以发现运行成功了。

这里要注意的事,调用toString的时候,中文在控制台打印的是字节,而调用对应属性的getter方法的时候,是可以做中文的判断的,显然这里protobuf的toString没有被修改好呀。

当然,我们也可以发现,netty本身已经封装好了对谷歌的protobuf的支持。Netty还是很强大的。

到此这篇关于Netty结合Protobuf进行编解码的文章就介绍到这了,更多相关Netty结合Protobuf编解码内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Java/Android 相关文章推荐
Java方法重载和方法重写的区别到底在哪?
Jun 11 Java/Android
Java elasticsearch安装以及部署教程
Jun 28 Java/Android
Spring Boot 整合 Apache Dubbo的示例代码
Jul 04 Java/Android
gateway网关接口请求的校验方式
Jul 15 Java/Android
spring boot中nativeQuery的用法
Jul 26 Java/Android
springboot 多数据源配置不生效遇到的坑及解决
Nov 17 Java/Android
关于MybatisPlus配置双数据库驱动连接数据库问题
Jan 22 Java/Android
Spring依赖注入多种类型数据的示例代码
Mar 31 Java/Android
Spring Boot 使用 Spring-Retry 进行重试框架
Apr 24 Java/Android
SpringBoot全局异常处理方案分享
May 25 Java/Android
SpringBoot项目多数据源及mybatis 驼峰失效的问题解决方法
Jul 07 Java/Android
MyBatis在注解上使用动态SQL方式(@select使用if)
Jul 07 Java/Android
Java常用工具类汇总 附示例代码
Java多条件判断场景中规则执行器的设计
Java基于字符界面的简易收银台
springboot集成flyway自动创表的详细配置
idea搭建可运行Servlet的Web项目
Java Dubbo框架知识点梳理
Java实现多线程聊天室
You might like
实现 win2003 下 mysql 数据库每天自动备份
2006/12/06 PHP
理解PHP5中static和const关键字的区别
2007/03/19 PHP
PHP 程序员也要学会使用“异常”
2009/06/16 PHP
PHP实现的限制IP投票程序IP来源分析
2016/05/04 PHP
详细解读php的命名空间(二)
2018/02/21 PHP
PHP中define() 与 const定义常量的区别详解
2019/06/25 PHP
jquery特效 幻灯片效果示例代码
2013/07/16 Javascript
js中数组Array的一些常用方法总结
2013/08/12 Javascript
深入剖析JavaScript中的枚举功能
2014/03/06 Javascript
jquery中ready()函数执行的时机和window的load事件比较
2015/06/22 Javascript
JavaScript中日期的相关操作方法总结
2015/10/24 Javascript
全面解析Bootstrap排版使用方法(标题)
2015/11/30 Javascript
jQuery插件开发精品教程让你的jQuery提升一个台阶
2016/01/27 Javascript
Bootstrap编写一个同时适用于PC、平板、手机的登陆页面
2016/06/30 Javascript
Vue实现双向绑定的方法
2016/12/22 Javascript
js实现文字跑马灯效果
2017/02/23 Javascript
Node.JS中快速扫描端口并发现局域网内的Web服务器地址(80)
2017/09/18 Javascript
利用nvm管理多个版本的node.js与npm详解
2017/11/02 Javascript
微信小程序实现的图片保存功能示例
2019/04/24 Javascript
JavaScript数组排序小程序实现解析
2020/01/13 Javascript
使用nodeJS中的fs模块对文件及目录进行读写,删除,追加,等操作详解
2020/02/06 NodeJs
聊聊vue 中的v-on参数问题
2021/01/29 Vue.js
[06:16]第十四期-国士无双绝地翻盘之撼地神牛
2014/06/24 DOTA
Python3控制路由器——使用requests重启极路由.py
2016/05/11 Python
Python编程实现删除VC临时文件及Debug目录的方法
2017/03/22 Python
Python排序搜索基本算法之归并排序实例分析
2017/12/08 Python
Python扩展内置类型详解
2018/03/26 Python
Python+selenium 获取浏览器窗口坐标、句柄的方法
2018/10/14 Python
Python实现最大子序和的方法示例
2019/07/05 Python
PyTorch在Windows环境搭建的方法步骤
2020/05/12 Python
快速了解Python开发环境Spyder
2020/06/29 Python
美国皮靴公司自1863年:The Frye Company
2016/11/30 全球购物
Dr.Jart+美国官网:韩国药妆品牌
2019/01/18 全球购物
学生会干部自荐信
2014/02/04 职场文书
2016年母亲节寄语
2015/12/04 职场文书
Redis如何一键部署脚本
2021/04/12 Redis