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 packageif (!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 classString 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;elsereturn 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;}//这里当做非kdoccomments = 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 extendscom.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;@Overridepublic 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.rhkb.cn/news/98523.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++笔记】C++之类与对象(中)

【C笔记】C之类与对象&#xff08;中&#xff09; 1、类的构造函数1.1、构造函数的基本用法1.2、构造函数的7个特性 2、类的析构函数2.1、析构函数的基本用法2.2、析构函数的6个特性 3、类的拷贝构造函数3.1、拷贝构造的基本用法3.2、拷贝构造的“无限套娃”陷阱3.3、深拷贝与浅…

二叉树搜索

✅<1>主页&#xff1a;我的代码爱吃辣&#x1f4c3;<2>知识讲解&#xff1a;数据结构——二叉搜索树☂️<3>开发环境 &#xff1a;Visual Studio 2022&#x1f4ac;<4>前言&#xff1a;在之前的我们已经学过了普通二叉树&#xff0c;了解了基本的二叉树…

vue导出文件流获取附件名称并下载(在response.headers里解析filename导出)

导出文件流下载&#xff0c;拦截器统一处理配置 需求以往实现的方法&#xff08;各自的业务层写方法&#xff09;现在实现的方法&#xff08;axios里拦截器统一配置处理&#xff09;把文章链接复制粘贴给后端&#xff0c;让大佬自己赏阅。 需求 之前实现的导出都是各自的业务层…

springboot之多数据源配置

文章目录 一、多数据源的典型使用场景1 业务复杂&#xff08;数据量大&#xff09;2 读写分离 二、如何实现多数据源通过AbstractRoutingDataSource动态指定数据源多数据源切换方式AOPMyBatis插件 三、spring集成多个Mybatis框架 实现多数据源控制四、dynamic-datasource 多数据…

01.Django入门

1.创建项目 1.1基于终端创建Django项目 打开终端进入文件路径&#xff08;打算将项目放在哪个目录&#xff0c;就进入哪个目录&#xff09; E:\learning\python\Django 执行命令创建项目 F:\Anaconda3\envs\pythonWeb\Scripts\django-admin.exe&#xff08;Django-admin.exe所…

残差网络实现

代码中涉及的图片实验数据下载地址&#xff1a;https://download.csdn.net/download/m0_37567738/88235543?spm1001.2014.3001.5501 代码&#xff1a; import torch import torch.nn as nn import torch.nn.functional as F #from utils import load_data,get_accur,train i…

茂名 湛江阳江某学校 ibm x3850服务器维修经历

简介&#xff1a;中国广东省阳江市某中学联想 IBM System x3850 x6服务器维修 io板故障处理经历分享&#xff1a; 这一天一位阳江的老师经其他学校老师介绍推荐对接我&#xff0c;说他们学校有一台ibm服务器出问题了&#xff0c;老师大致跟我描述了一下这台服务器发生故障的前…

Android12之com.android.media.swcodec无法生成apex问题(一百六十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Apache DolphinScheduler 支持使用 OceanBase 作为元数据库啦!

DolphinScheduler是一个开源的分布式任务调度系统&#xff0c;拥有分布式架构、多任务类型、可视化操作、分布式调度和高可用等特性&#xff0c;适用于大规模分布式任务调度的场景。目前DolphinScheduler支持的元数据库有Mysql、PostgreSQL、H2&#xff0c;如果在业务中需要更好…

iOS UIAlertController控件

ios 9 以后 UIAlertController取代UIAlertView和UIActionSheet UIAlertControllerStyleAlert和UIAlertControllerStyleActionSheet。 在UIAlertController中添加按钮和关联输入框 UIAlertAction共有三种类型&#xff0c;默认&#xff08;UIAlertActionStyleDefault&#xff0…

【Linux】进程信号篇Ⅰ:信号的产生(signal、kill、raise、abort、alarm)、信号的保存(core dump)

文章目录 一、 signal 函数&#xff1a;用户自定义捕捉信号二、信号的产生1. 通过中断按键产生信号2. 调用系统函数向进程发信号2.1 kill 函数&#xff1a;给任意进程发送任意信号2.2 raise 函数&#xff1a;给调用进程发送任意信号2.3 abort 函数&#xff1a;给调用进程发送 6…

机器学习深度学习——NLP实战(情感分析模型——数据集)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——BERT&#xff08;来自transformer的双向编码器表示&#xff09; &#x1f4da;订阅专栏&#xff1a;机器…

Kubernetes 安全机制 认证 授权 准入控制

客户端应用若想发送请求到 apiserver 操作管理K8S资源对象&#xff0c;需要先通过三关安全验证 认证&#xff08;Authentication&#xff09;鉴权&#xff08;Authorization&#xff09;准入控制&#xff08;Admission Control&#xff09; Kubernetes 作为一个分布式集群的管理…

Flink的Standalone部署实战

在Flink是通用的框架&#xff0c;以混合和匹配的方式支持部署不同场景&#xff0c;而Standalone单机部署方便快速部署&#xff0c;记录本地部署过程&#xff0c;方便备查。 环境要求 1&#xff09;JDK1.8及以上 2&#xff09;flink-1.14.3 3&#xff09;CentOS7 Flink相关信…

ELK日志监控系统搭建docker版

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

opencv-进阶05 手写数字识别原理及示例

前面我们仅仅取了两个特征维度进行说明。在实际应用中&#xff0c;可能存在着更多特征维度需要计算。 下面以手写数字识别为例进行简单的介绍。 假设我们要让程序识别图 20-2 中上方的数字&#xff08;当然&#xff0c;你一眼就知道是“8”&#xff0c;但是现在要让计算机识别…

【JUC系列-01】深入理解JMM内存模型的底层实现原理

一&#xff0c;深入理解JMM内存模型 1&#xff0c;什么是可见性 在谈jmm的内存模型之前&#xff0c;先了解一下并发并发编程的三大特性&#xff0c;分别是&#xff1a;可见性&#xff0c;原子性&#xff0c;有序性。可见性指的就是当一个线程修改某个变量的值之后&#xff0c…

自动化测试用例设计实例

在编写用例之间&#xff0c;笔者再次强调几点编写自动化测试用例的原则&#xff1a; 1、一个脚本是一个完整的场景&#xff0c;从用户登陆操作到用户退出系统关闭浏览器。 2、一个脚本脚本只验证一个功能点&#xff0c;不要试图用户登陆系统后把所有的功能都进行验证再退出系统…

智慧水利利用4G物联网技术实现远程监测、控制、管理

智慧水利工业路由器是集合数据采集、实时监控、远程管理的4G物联网通讯设备&#xff0c;能够让传统水利系统实现智能化的实时监控和远程管理。工业路由器利用4G无线网络技术&#xff0c;能够实时传输数据和终端信息&#xff0c;为水利系统的运维提供有效的支持。 智慧水利系统是…

湘潭大学 湘大 XTU OJ 1055 整数分类 题解(非常详细)

链接 整数分类 题目 Description 按照下面方法对整数x进行分类&#xff1a;如果x是一个个位数&#xff0c;则x属于x类&#xff1b;否则将x的各位上的数码累加&#xff0c;得到一个新的x&#xff0c;依次迭代&#xff0c;可以得到x的所属类。比如说24&#xff0c;246&#…