protobuf+netty自定义编码解码

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";
    }

}

本文原创,转载请申明

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode--HOT100题(38)

目录 题目描述&#xff1a;226. 翻转二叉树&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;226. 翻转二叉树&#xff08;简单&#xff09; 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 LeetCode做题链…

lvs-DR模式:

lvs-DR数据包流向分析 客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 Director Server 和 Real Server 在同一个网络中&#xff0c;数据通过二层数据链路…

7-42 整型关键字的散列映射

题目链接&#xff1a;这里 题目大意&#xff1a;就是写一个线性探测的散列 然鹅&#xff0c;我不会写(?)我一共错了两个地方 有冲突的情况下&#xff0c;就是线性探查然后往后找&#xff0c;但是我之前写的是t&#xff0c;应该是t (t1)%p;…在有重复关键字的时候&#xff0c…

大学生创业出路【第二弹】科创训练营

目录 &#x1f680;一、我从哪里了解到的训练营 &#x1f680;二、训练营里学习和日常 &#x1f50e;学习 &#x1f50e;环境和设备 &#x1f50e;遇到的人 &#x1f50e;团队记录视频 &#x1f680;三、感悟 ​​​​个人主页&#xff1a;一天三顿-不喝奶茶&#x1f39…

UE4/5Niagara粒子特效之Niagara_Particles官方案例:1.5->2.3

目录 之前的文章&#xff1a; 1.5 Blend Attributes by Value 发射器更新 粒子生成 粒子更新 2.1 Static Beams ​编辑 发射器更新&#xff1a; 粒子生成 粒子更新 2.2 Dynamic Beams 没有开始模拟前的效果是&#xff1a; 开始模拟后的效果是&#xff1a; 发射器更新 …

数据结构入门 — 顺序表详解

前言 数据结构入门 — 顺序表详解 博客主页链接&#xff1a;https://blog.csdn.net/m0_74014525 关注博主&#xff0c;后期持续更新系列文章 文章末尾有源码 *****感谢观看&#xff0c;希望对你有所帮助***** 文章目录 前言一、顺序表1. 顺序表是什么2. 优缺点 二、概念及结构…

java-IONIO

一、JAVA IO 1.1. 阻塞 IO 模型 最传统的一种 IO 模型&#xff0c;即在读写数据过程中会发生阻塞现象。当用户线程发出 IO 请求之后&#xff0c;内 核会去查看数据是否就绪&#xff0c;如果没有就绪就会等待数据就绪&#xff0c;而用户线程就会处于阻塞状态&#xff0c;用户线…

java八股文面试[数据结构]——ArrayList和LinkedList区别

ArrayList和LinkedList的异同 二者的线程都不安全&#xff0c;相对线程安全的Vector,执行效率高。此外&#xff0c;ArrayList时实现了基于动态数组的数据结构&#xff0c;LinkedList基于链表的数据结构&#xff0c;对于随机访问get和set&#xff0c;ArrayList觉得优于LinkedLis…

线性回归的正则化改进(岭回归、Lasso、弹性网络),最小二乘法和最大似然估计之间关系,正则化

目录 最小二乘法 极大似然估计的思想 概率&#xff1a;已知分布参数-对分布参数进行估计 概率描述的是结果;似然描述的是假设/模型​编辑 似然&#xff1a;已知观测结果-对分布参数进行估计​编辑 对数函数消灭连乘-连乘导致算法参数消失 极大似然估计公式&#xff1a;将乘…

LeetCode:Hot100python版本之回溯

回溯算法其实是纯暴力搜索。for循环嵌套是写不出的 组合&#xff1a;没有顺序 排列&#xff1a;有顺序 回溯法可以抽象为树形结构。只有在回溯算法中递归才会有返回值。 46. 全排列 排列是有顺序的。 组合类问题用startindex&#xff0c;排序类问题用used&#xff0c;来标…

【网络】DNS | ICMP | NAT | 代理服务器

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《网络》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 前面几篇文章虽然讲介绍了整个网络通信的协议栈&#xff0c;我们也知道了完整的网络通信过程&#xff…

【图像去噪】基于混合自适应(EM 自适应)实现自适应图像去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

如何拉取Gitee / GitHub上的Unity项目并成功运行

前言 由于目前大部分人使用的仓库都是Gitee或者是GitHub&#xff0c;包括小编的公司所使用的项目仓库也包括了Gitee&#xff1b;我们需要学习技术栈时都会去百度或者是去GitHub上看看别人的项目观摩学习&#xff0c;可能很多小白在遇到拉取代码时出现各种问题&#xff0c;或者…

Server2016安装SQL server数据库遇到异常解决

首先看几个会出现的异常&#xff0c;下边看解决办法&#xff1a; 第一步: 先修改安装包x86\setup目录下的setupsql.exe,以Xp&#xff0c;SP3兼容模式运行&#xff0c; 这个右键&#xff0c;属性&#xff0c;兼容性&#xff0c;修改就行&#xff0c;类似这样 第二步: 修改c:…

【Rust】Rust学习 第十六章无畏并发

安全且高效的处理并发编程是 Rust 的另一个主要目标。并发编程&#xff08;Concurrent programming&#xff09;&#xff0c;代表程序的不同部分相互独立的执行&#xff0c;而 并行编程&#xff08;parallel programming&#xff09;代表程序不同部分于同时执行&#xff0c;这两…

【优选算法】—— 字符串匹配算法

在本期的字符串匹配算法中&#xff0c;我将给大家带来常见的两种经典的示例&#xff1a; 1、暴力匹配&#xff08;BF&#xff09;算法 2、KMP算法 目录 &#xff08;一&#xff09;暴力匹配&#xff08;BF&#xff09;算法 1、思想 2、演示 3、代码展示 &#xff08;二&…

大数据课程K2——Spark的RDD弹性分布式数据集

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解Spark的RDD结构; ⚪ 掌握Spark的RDD操作方法; ⚪ 掌握Spark的RDD常用变换方法、常用执行方法; 一、Spark最核心的数据结构——RDD弹性分布式数据集 1. 概述 初学Spark时,把RDD看…

【微服务】spring 条件注解从使用到源码分析详解

目录 一、前言 二、spring 条件注解概述 2.1 条件注解Conditional介绍 2.2 Conditional扩展注解 2.2.1 Conditional扩展注解汇总 三、spring 条件注解案例演示 3.1 ConditionalOnBean 3.2 ConditionalOnMissingBean 3.2.1 使用在类上 3.2.2 使用场景补充 3.3 Condit…

如何使用 Docker Compose 运行 OSS Wordle 克隆

了解如何使用 Docker Compose 在五分钟内运行您自己的流行 Wordle 克隆实例。您将如何部署 Wordle&#xff1f; Wordle在 2021 年底发布后席卷了互联网。对于许多人来说&#xff0c;这仍然是一种早晨的仪式&#xff0c;与一杯咖啡和一天的开始完美搭配。作为一名 DevOps 工程师…

开源TTS+gtx1080+cuda11.7+conda+python3.9吊打百度TTS

一、简介 开源项目&#xff0c;文本提示的生成音频模型 https://github.com/suno-ai/bark Bark是由Suno创建的基于变换器的文本到音频模型。Bark可以生成极为逼真的多语种演讲以及其他音频 - 包括音乐、背景噪音和简单的声音效果。该模型还可以产生非言语沟通&#xff0c;如…