protobuf+netty自定义编码解码

news2024/12/23 9:29:20

protobuf+netty自定义编

项目背景

protobuf+netty自定义编码解码

比如心跳协议,客户端请求的协议是10001,在java端如何解码,心跳返回协议如何编码,将协议号带过去

// 心跳包
//10001
message c2s_heartbeat {
}

//10002
message s2c_heartbeat {
  int64 timestamp = 1;    // 时间戳 ms
}

解决方案

1.每个协议id换个生成的class类名关联起来,使用的时候使用读取文件

2.使用jprotobuf 把注释上面的协议id带入到生成文件里面

使用protoc生成java文件的时候带上自定注解

<dependency>
			<groupId>com.baidu</groupId>
			<artifactId>jprotobuf</artifactId>
			<version>2.4.15</version>
		</dependency>

重写根据proto文件生成java代码的方法百度版本的核心文件在ProtobufIDLProxy类

重写核心方法 createCodeByType 生成代码的核心方法

	private static CodeDependent createCodeByType(ProtoFile protoFile, MessageElement type, Set<String> enumNames,
                                                  boolean topLevelClass, List<TypeElement> parentNestedTypes, List<CodeDependent> cds, Set<String> packages,
                                                  Map<String, String> mappedUniName, boolean isUniName) {
        //...省略

if (topLevelClass) {
            // define package
            if (!StringUtils.isEmpty(packageName)) {
                code.append("package ").append(packageName).append(CODE_END);
                code.append("\n");
            }
            // add import;
            code.append("import com.baidu.bjf.remoting.protobuf.FieldType;\n");
            code.append("import com.baidu.bjf.remoting.protobuf.EnumReadable;\n");
            code.append("import com.baidu.bjf.remoting.protobuf.annotation.Protobuf;\n");

        }
		//添加自定义操作
        generateCommentsForClass(code,type,protoFile);

        // define class
        String clsName;
        if (topLevelClass) {
            clsName = "public class ";
        } else {
            clsName = "public static class ";
        }
/**
     * 生成class注释
     * @param code 当前代码
     * @param type 当前类型
     * @param protoFile 所有类型
     * @return 是否返回协议码
     */
    private static void generateCommentsForClass(StringBuilder code, MessageElement type, ProtoFile protoFile) {
        TypeElement typeElement = protoFile.typeElements().stream().filter(i -> i.name().equals(type.name())).findFirst().orElse(null);
        if(typeElement==null){
            return;
        }
        String documentation = typeElement.documentation();
        if(StringUtils.isEmpty(documentation)){
            documentation = "";
        }else {
            documentation = documentation.trim();
        }
        String[] split = documentation.split("\n");
        Integer protoId = null;
        try{
            protoId = Integer.parseInt(split[split.length-1]);
            String collect = Arrays.stream(split).collect(Collectors.toList()).subList(0, split.length - 1).stream().collect(Collectors.joining());

            //code.append("import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;\n");

            String comment = """
                
                /**
                 * %d
                 * %s 
                 * @author authorZhao
                 * @since %s
                 */
                """;

            comment = String.format(comment,protoId,collect,DATE);
            code.append(comment);
            code.append("@com.git.ProtoId("+protoId+")";
        }catch (Exception e){
            String comment = """
                
                /**
                 * %s
                 * @author authorZhao
                 * @since %s
                 */
                """;
            comment = String.format(comment,documentation,DATE);
            code.append(comment);
        }



        /*code.append("    /**").append(ClassCode.LINE_BREAK);
        code.append("     * ").append(documentation).append(ClassCode.LINE_BREAK);
        code.append("     * ").append(ClassCode.LINE_BREAK);*/
        //code.append("     */").append(ClassCode.LINE_BREAK);
    }

用法

public static void main(String[] args) {
        File javaOutPath = new File("E:\\java\\workspace\\proto\\src\\main\\java");
        javaOutPath = new File("C:\\Users\\Admin\\Desktop\\工作文档\\worknote\\java");
        File protoDir = new File("E:\\project\\git\\test_proto");
        //protoDir = copy(protoDir);
        //filterFile(protoDir);

        File protoFile = new File(protoDir.getAbsolutePath()+"/activity.proto");
        MyProtobufIDLProxy.setFormatJavaField(true);
        try {
            //这里改写之后可以根据一个proto文件生成所有的文件
            MyProtobufIDLProxy.createAll(protoFile,protoDir, javaOutPath);
            System.out.println("create success. input file="+protoFile.getName()+"\toutput path=" + javaOutPath.getAbsolutePath());
        } catch (IOException var5) {
            System.out.println("create failed: " + var5.getMessage());
        }
        System.exit(0);
    }

3.重写protobuf的核心文件protoc

以windows为例

git clone https://github.com/protocolbuffers/protobuf.git
cd protobuf
git submodule update --init --recursive

本文使用clion开发环境,找到核心代码


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JqHH80Ca-1692518750738)(http://opadmin.pingyuanren.top/file/png/2023/df57d1bdb3c14744b7bad9034b1c827a.png)]


SourceLocation location;
  if (descriptor->GetSourceLocation(&location)) {
    WriteDocCommentBodyForLocation(printer, location, kdoc);
  }
std::string comments = location.leading_comments.empty()
                             ? location.trailing_comments
                             : location.leading_comments;
  if (!comments.empty()) {
    if (kdoc) {
      comments = EscapeKdoc(comments);
    } else {
      comments = EscapeJavadoc(comments);
    }

    std::vector<std::string> lines = absl::StrSplit(comments, "\n");
    while (!lines.empty() && lines.back().empty()) {
      lines.pop_back();
    }

    if (kdoc) {
      printer->Print(" * ```\n");
    } else {
      printer->Print(" * <pre>\n");
    }

    for (int i = 0; i < lines.size(); i++) {
      // Most lines should start with a space.  Watch out for lines that start
      // with a /, since putting that right after the leading asterisk will
      // close the comment.
      if (!lines[i].empty() && lines[i][0] == '/') {
        printer->Print(" * $line$\n", "line", lines[i]);
      } else {
        printer->Print(" *$line$\n", "line", lines[i]);
      }
    }

    if (kdoc) {
      printer->Print(" * ```\n");
    } else {
      printer->Print(" * </pre>\n");
    }
    printer->Print(" *\n");
  }

重写方法 WriteMessageDocComment 把注释的最后一行协议号提取出来增加一个协议id

void WriteMessageDocComment(io::Printer* printer, const Descriptor* message,
                            const bool kdoc) {
  printer->Print("/**\n");
  WriteDocCommentBody(printer, message, kdoc);
  if (kdoc) {
    printer->Print(
        " * Protobuf type `$fullname$`\n"
        " */\n",
        "fullname", EscapeKdoc(message->full_name()));
  } else {
    printer->Print(
        " * Protobuf type {@code $fullname$}\n"
        " */\n",
        "fullname", EscapeJavadoc(message->full_name()));
  }
}

简单改写一下


       //网上抄袭的
    bool isNum(const std::string& str){
        std::stringstream sin(str);
        double t;
        char p;
        if(!(sin >> t))
            /*解释:
                sin>>t表示把sin转换成double的变量(其实对于int和float型的都会接收),如果转换成功,则值为非0,如果转换不成功就返回为0
            */
            return false;
        if(sin >> p)
            /*解释:此部分用于检测错误输入中,数字加字符串的输入形式(例如:34.f),在上面的的部分(sin>>t)已经接收并转换了输入的数字部分,在stringstream中相应也会把那一部分给清除,如果此时传入字符串是数字加字符串的输入形式,则此部分可以识别并接收字符部分,例如上面所说的,接收的是.f这部分,所以条件成立,返回false;如果剩下的部分不是字符,那么则sin>>p就为0,则进行到下一步else里面
              */
            return false;
        else
            return true;
    }

    /**
    * 生成自定义代码
    * @param printer
    * @param message
    * @param kdoc
    * */
    void writeWithProtoId(io::Printer *printer, const Descriptor *message) {
        SourceLocation location;

        bool hasComments = message->GetSourceLocation(&location);
        if (!hasComments) {
            return;
        }

        std::string comments = location.leading_comments.empty()? location.trailing_comments: location.leading_comments;
        if (comments.empty()) {
            return;
        }

        //这里当做非kdoc
        comments = EscapeJavadoc(comments);

        //根据换行分割
        std::vector<std::string> lines = absl::StrSplit(comments, "\n");
        while (!lines.empty() && lines.back().empty()) {
            lines.pop_back();
        }
        if(lines.empty()){
            return;
        }
        std::string protoId = lines[lines.size()-1];
        if(!isNum(protoId)){
            return;
        }
        printer->Print("@com.git.protoId($line$)\n","line",protoId);
    }


void WriteMessageDocComment(io::Printer* printer, const Descriptor* message,
                            const bool kdoc) {
  printer->Print("/**\n");
  WriteDocCommentBody(printer, message, kdoc);
  if (kdoc) {
    printer->Print(
        " * Protobuf type `$fullname$`\n"
        " */\n",
        "fullname", EscapeKdoc(message->full_name()));
  } else {
      printer->Print(
              " * Protobuf type {@code $fullname$}\n"
              " */\n",
              "fullname", EscapeJavadoc(message->full_name()));
      writeWithProtoId(printer,message);
  }
}

protoc.exe --plugin=protoc-gen-grpc-java=./protoc-gen-grpc-java-1.57.1-windows-x86_64.exe --proto_path=./proto ./proto*.proto --java_out=./test --grpc-java_out=./test

最后生成的代码

 /**
   * <pre>
   *身份验证c2s
   *10007
   * </pre>
   *
   * Protobuf type {@code login.c2s_auth}
   */
  @com.git.protoId(10007)
  public static final class c2s_auth extends
      com.google.protobuf.GeneratedMessageV3 implements
      // @@protoc_insertion_point(message_implements:login.c2s_auth)
      c2s_authOrBuilder {

使用方式

本文结合spring扫描,

/**
 * 这个类并不注册什么bean,仅仅扫描protoBuf
 * ProtoScan类似于mybatis的scan,表示proto生成的java文件所在目录
 * 扫描处理protoId
 */
@Slf4j
public class BeanMapperSelector implements ImportBeanDefinitionRegistrar {

    /**
     * 扫描的包路径
     */
    private String[] basePackage;
    /**
     * 需要扫描的类
     */
    private Class[] classes;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ProtoScan.class.getName());
        this.basePackage = (String[])annotationAttributes.get("basePackages");

        this.classes = (Class[])annotationAttributes.get("classes");

        List<Class> classList = new ArrayList<>();
        for (Class aClass : classes) {
            if(aClass.isAnnotationPresent(ProtoId.class) && com.google.protobuf.GeneratedMessageV3.class.isAssignableFrom(aClass)){
                classList.add(aClass);
            }
        }
        if(basePackage.length>0){
            List<String> list = List.of(basePackage).stream().map(this::resolveBasePackage).toList();
            List<Class> classes1 = ClassScanUtil.scanPackageClass(list, null, clazz -> clazz.isAnnotationPresent(ProtoId.class) && com.google.protobuf.GeneratedMessageV3.class.isAssignableFrom(clazz));
            classList.addAll(classes1);
        }
        for (Class aClass : classList) {
            try {
                ProtoId protoId = AnnotationUtils.getAnnotation(aClass, ProtoId.class);
                if(aClass.getSimpleName().startsWith("c2s")){
                    //将byte[]转化为对象的方法缓存
                    //com.google.protobuf.GeneratedMessageV3 protoObject = (com.google.protobuf.GeneratedMessageV3) method
                    .invoke(null, bytes);
                    Method m = aClass.getMethod("parseFrom", byte[].class);
                    AppProtocolManager.putProtoIdC2SMethod(protoId.value(),m);
                }else {
                    //class->protoId映射缓存
                    AppProtocolManager.putOldProtoIdByClass(protoId.value(),aClass);
                }
            }catch (Exception e){
                log.error("protoId 注册失败",e);
            }
        }
        //
        AppProtocolManager.info();
    }


    protected String resolveBasePackage(String basePackage) {
        String replace = basePackage.replace(".", "/");
        return ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX+replace+"/*.class";
    }

}

本文原创,转载请申明

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/903156.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据分享】2006-2021年我国省份级别的市政公用设施建设固定资产投资相关指标(30多项指标)

《中国城市建设统计年鉴》中细致地统计了我国城市市政公用设施建设与发展情况&#xff0c;在之前的文章中&#xff0c;我们分享过基于2006-2021年《中国城市建设统计年鉴》整理的2006—2021年我国省份级别的市政设施水平相关指标、2006-2021年我国省份级别的各类建设用地面积数…

02.案列项目Demo

1.创建项目 1. 创建项目 用pycharm 选择对应的编译器&#xff0c;输入对应的文件名&#xff0c;点击创建项目。删除默认外层生成的template和DIRS 配置项&#xff1a; 2. 创建App 创建appo1的命令&#xff1a; python manage.py startapp app01 如果使用pycharm>tool>…

Qt项目报错:Cannot run compiler ‘clang++‘. /bin/sh: 1: clang++: not found

在一台旧电脑上装了深度系统&#xff0c;装了Qt&#xff0c;导入项目&#xff0c; build提示 clang找不到&#xff1a; Project ERROR: Cannot run compiler clang. Output: /bin/sh: 1: clang: not found Maybe you forgot to setup the environment? Error while parsing …

​8th参考文献:[8]许少辉.乡村振兴战略下传统村落文化旅游设计[M]北京:中国建筑工业出版社,2022.

​&#xff18;th参考文献&#xff1a;&#xff3b;&#xff18;&#xff3d;许少辉&#xff0e;乡村振兴战略下传统村落文化旅游设计&#xff3b;&#xff2d;&#xff3d;北京&#xff1a;中国建筑工业出版社&#xff0c;&#xff12;&#xff10;&#xff12;&#xff12;&a…

2022数学建模国赛C题官网展示论文C155论文复现

2022数学建模国赛C题C155论文复现 1.内容比对2.第一问第二小问复现代码2.1 页表合并2.2 数据的正态性检验2.2.1数据的正态性检验效果图 2.3不满足正态性&#xff0c;进行中心化对数比变换2.3.1 核心步骤-inf用0值替换2.3.2中心化对数比变换效果图 2.4描述性统计2.5 箱线图绘制 …

回归预测 | MATLAB实现TSO-LSSVM金枪鱼群算法优化最小二乘支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现TSO-LSSVM金枪鱼群算法优化最小二乘支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现TSO-LSSVM金枪鱼群算法优化最小二乘支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&a…

五种网络IO模型

五种模型出自&#xff1a;RFC标准。可参考&#xff1a; 《UNIX网络编程-卷一》 6.2 很多程序员是从高级语言的网络编程/文件操作了解到nio&#xff0c;继而了解到五种io模型的&#xff1b; 这五种io模型不止用于网络io “阻塞与****系统调用”是怎么回事&#xff1f;我知道了线…

Redis之持久化机制

文章目录 一、redis持久化二、持久化方式2.1. RDB方式2.1.1 RDB手动2.1.2 RDB自动2.1.3RDB优缺点 2.2AOF方式2.2.1 AOF写数据遇到的问题2.2.2 AOF重写方式 二、RDB和AOF优缺点对比总结 一、redis持久化 Redis 是内存数据库&#xff0c;如果不将内存中的数据库状态保存到磁盘&a…

Azure防火墙

文章目录 什么是Azure防火墙如何部署和配置创建虚拟网络创建虚拟机创建防火墙创建路由表&#xff0c;关联子网、路由配置防火墙策略配置应用程序规则配置网络规则配置 DNAT 规则 更改 Srv-Work 网络接口的主要和辅助 DNS 地址测试防火墙 什么是Azure防火墙 Azure防火墙是一种用…

ELK日志监控系统搭建docker版

目录 日志来源elk介绍elasticsearch介绍logstash介绍kibana介绍 部署elasticsearch拉取镜像&#xff1a;docker pull elasticsearch:7.17.9修改配置⽂件&#xff1a;/usr/share/elasticsearch/config/elasticsearch.yml启动容器设置密码&#xff08;123456&#xff09;忘记密码…

Redis从基础到进阶篇(一)

目录 一、了解NoSql 1.1 什么是Nosql 1.2 为什么要使用NoSql 1.3 NoSql数据库的优势 1.4 常见的NoSql产品 1.5 各产品的区别 二、Redis介绍 2.1什么是Redis 2.2 Redis优势 2.3 Redis应用场景 2.4 Redis下载 三、Linux下安装Redis 3.1 环境准备 3.2 Redis的…

Win11右键显示更多选项

不需要重启电脑&#xff0c;重启资源管理器即可&#xff0c;用命令&#xff1a;taskkill /f /im explorer.exe & start explorer.exe

一、Kafka概述

目录 1.3 Kafka的基础架构 1.3 Kafka的基础架构 Producer&#xff1a;消息生产者&#xff0c;就是向 Kafka broker 发消息的客户端Consumer&#xff1a;消息消费者&#xff0c;向 Kafka broker 取消息的客户端。Consumer Group&#xff08;CG&#xff09;&#xff1a;消费者组&…

浅析深浅拷贝

我们在对对象进行复制时就用到深浅拷贝。 一、普通复制 <script>const people{name:tim,age:22}const testpeople;console.log(test);//tim 22test.age20;console.log(test);//tim 20console.log(people);//tim 20 </script> 控制台打印结果&#xff1a; 之所以…

使用struct解析通达信本地Lday日线数据

★★★★★博文原创不易&#xff0c;我的博文不需要打赏&#xff0c;也不需要知识付费&#xff0c;可以白嫖学习编程小技巧&#xff0c;喜欢的老铁可以多多帮忙点赞&#xff0c;小红牛在此表示感谢。★★★★★ 在Python中&#xff0c;struct模块提供了二进制数据的打包和解包…

使用transformers生成文本Generating text with transformers

到目前为止&#xff0c;您已经看到了Transformers架构内部的一些主要组件的高级概述。但您还没有看到从头到尾的整体预测过程是如何工作的。让我们通过一个简单的例子来了解。在这个例子中&#xff0c;您将查看一个翻译任务或一个序列到序列的任务&#xff0c;这恰好是Transfor…

破解难题:如何应对项目中的‘老油条’障碍

引言 在项目管理的实践中&#xff0c;我们经常遇到各种各样的人员挑战。其中&#xff0c;有一种特殊的挑战被称为“老油条”现象。这些“老油条”通常在表面上表现得非常配合&#xff0c;但在实际工作中却常常没有任何进展。这种情况不仅会影响项目的进度&#xff0c;还可能对…

机器学习---常见的距离公式(欧氏距离、曼哈顿距离、标准化欧式距离、余弦距离、杰卡德距离、马氏距离、切比雪夫距离、闵可夫斯基距离、K-L散度)

1. 欧氏距离 欧几里得度量&#xff08;euclidean metric&#xff09;&#xff08;也称欧氏距离&#xff09;是一个通常采用的距离定义&#xff0c;指在m维空 间中两个点之间的真实距离&#xff0c;或者向量的自然长度&#xff08;即该点到原点的距离&#xff09;。在二维和三维…

Spring(16) Aware结尾的类整理

目录 一、什么是 Aware 结尾的类&#xff1f;二、常见的 Aware 实现接口三、Aware 实现原理 一、什么是 Aware 结尾的类&#xff1f; 在 Spring Boot 中&#xff0c;以 Aware 结尾的类通常是一些继承了 Aware 接口的接口类&#xff0c;它们用于使 Bean 获取某些特定的能力或资…