gRPC结合vcpkg在x64-windows平台visual studio2019 cmake工程里面的应用

这里我们运用vcpkg去下载安装gRPC,进入vcpkg目录后,执行命令:.\vcpkg.exe install grpc:x64-windows

grpc在vcpkg里面安装完成后,我们就来使用grpc做一个简单的例子。

gRPC顾名思义,就是google的RPC方案,基于protobuf数据传输,其中proto文件的定义约定了服务器端和客户端的服务接口协议。

这里我们就用加法和乘法作为服务器端提供的服务,让客户端去调用(RPC),我们建立三个文件夹CPP_Server, CPP_Client, proto 来分别存储服务器端代码,客户端代码,以及proto文件。项目配置选用cmakelist.txt和cmake来管理。

1.  服务器端和客户端的proto定义(calculation.proto文件):

syntax = "proto3";
package data_handler;

service CalculationInterface{
    // Add operation
    rpc Add(AddRequest) returns (AddReply){}
    // Multiply operation
    rpc Multiply(MultiplyRequest) returns (MultiplyReply){}
}

message AddReply{
    int32 result = 1;
}

message AddRequest{
    int32 param1 = 1;
    int32 param2 = 2;
}

message MultiplyReply{
    int32 result = 1;
}

message MultiplyRequest{
    int32 param1 = 1;
    int32 param2 = 2;
}

 2. 服务器端代码

在服务器端,我们要在cmakelist里面进行proto文件的解析执行成相应的.pb.cc,.pb.h,.grpc.pb.cc,.grpc.pb.h文件,同时对项目文件的配置。

那么我们必然要先找到grpc, protobuf库和执行文件。这时候就需要用到vcpkg这套包管理器,

而下面这句话就是让vcpkg的包管理起作用的关键:

set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")

注意这句话一定要在定义project名字之前,本例子是:project(CalculationInGrpcServer)

这样子后面的find_package, find_program, target_link_libraries等都会去vckpg里面找到。

cmake_minimum_required(VERSION 3.20)

# Note: 8 target(s) were omitted.


message("--------" $ENV{VCPKG_ROOT})

set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")
project(CalculationInGrpcServer)

set(_GRPC_GRPCPP gRPC::grpc++)
set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf)
set(_REFLECTION gRPC::grpc++_reflection)

set(_PROTOBUF_LIBPROTOBUF_D libprotobufd)
find_package(gRPC CONFIG REQUIRED)
find_program(_PROTOBUF_PROTOC protoc REQUIRED)
find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin REQUIRED)

# Generated proto sources
get_filename_component(proto "../proto/calculation.proto" ABSOLUTE)
get_filename_component(proto_name "../proto/calculation.proto" NAME_WE)
get_filename_component(proto_path "${proto}" PATH)

set(proto_srcs "${proto_path}/${proto_name}.pb.cc")
set(proto_hdrs "${proto_path}/${proto_name}.pb.h")
set(grpc_srcs "${proto_path}/${proto_name}.grpc.pb.cc")
set(grpc_hdrs "${proto_path}/${proto_name}.grpc.pb.h")
message("------------------------------------------------")
message(${_PROTOBUF_PROTOC})
message(${_GRPC_CPP_PLUGIN_EXECUTABLE})
message(${proto_path})

message("-------------------------------------------------")
add_custom_command(
      OUTPUT "${proto_srcs}" "${proto_hdrs}" "${grpc_srcs}" "${grpc_hdrs}"
      COMMAND ${_PROTOBUF_PROTOC}
      ARGS --grpc_out "${proto_path}"
        --cpp_out "${proto_path}"
        -I "${proto_path}"
        --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
        "${proto}"
      DEPENDS "${proto}")

# Include generated *.pb.h files
include_directories(
    "${proto_path}"
    )

file(GLOB PUBLIC_HEADER ${CMAKE_CURRENT_BINARY_DIR}/*.h
                        ${PROJECT_SOURCE_DIR}/../proto/*.h)


add_executable(${PROJECT_NAME} CalculationServer.cc ${proto_srcs} ${grpc_srcs})
target_link_libraries(${PROJECT_NAME} PRIVATE gRPC::gpr gRPC::upb gRPC::grpc gRPC::grpc++)

message("protobuf libs are:")
message(${_PROTOBUF_LIBPROTOBUF_D})

在我们的服务器端代码里面着重用到的是::data_handler::CalculationInterface::Service,这个是proto解释器帮我们对proto文件解析成cc文件后,里面的一个Service接口,我们代码里面最主要是去实现这个接口,来看看吧:


#include <grpcpp/grpcpp.h>
#include <grpcpp/security/server_credentials.h>
#include <grpcpp/server.h>
#include <grpcpp/server_builder.h>
#include <grpcpp/server_context.h>

#include <calculation.grpc.pb.h>
#include <calculation.pb.h>
#include <thread>

using grpc::Server;
using grpc::ServerBuilder;
using ::grpc::ServerContext;
using grpc::ServerReader;
using grpc::ServerReaderWriter;
using grpc::ServerWriter;
using grpc::Status;

class CalculationInGrpcServerImpl final
    : public ::data_handler::CalculationInterface::Service {
public:
  virtual ~CalculationInGrpcServerImpl(){};

  // Add operation
  ::grpc::Status Add(::grpc::ServerContext* context,
                             const ::data_handler::AddRequest* request,
                             ::data_handler::AddReply* response) override;
  // Multiply operation
  ::grpc::Status Multiply(
      ::grpc::ServerContext* context,
      const ::data_handler::MultiplyRequest* request,
      ::data_handler::MultiplyReply* response) override;
};

::grpc::Status CalculationInGrpcServerImpl::Add(
    ::grpc::ServerContext* context,
    const ::data_handler::AddRequest* request,
    ::data_handler::AddReply* response) {
  if (!context || !request || !response) {
    return ::grpc::Status::CANCELLED;
  }
  int32_t a = request->param1();
  int32_t b = request->param2();
  int32_t result = a + b;
  response->set_result(result);
  std::cout << "Add operation: " << a << " + " << b << std::endl;
  std::cout << "The result is: " << result << std::endl;
  return ::grpc::Status::OK;
}

::grpc::Status CalculationInGrpcServerImpl::Multiply(
    ::grpc::ServerContext* context,
    const ::data_handler::MultiplyRequest* request,
    ::data_handler::MultiplyReply* response) {
  if (!context || !request || !response) {
    return ::grpc::Status::CANCELLED;
  }
  int32_t a = request->param1();
  int32_t b = request->param2();
  int32_t result = a * b;
  response->set_result(result);
  std::cout << "Multiply operation: " << a << " * " << b << std::endl;
  std::cout << "The result is: " << result << std::endl;
  return ::grpc::Status::OK;
}

// define the gRPC server
std::unique_ptr<Server> server_ptr;
CalculationInGrpcServerImpl service;

void RunServer(const std::string& server_address) {
  ServerBuilder builder;
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  builder.RegisterService(&service);

  server_ptr = builder.BuildAndStart();
  std::cout << "Server(" << server_address << ") is listening on ..." << std::endl;
  std::cout << "Press 'q' to exit the server" << std::endl;

  server_ptr->Wait();
}

int main() { 

    std::string server_address("0.0.0.0:50051");
    std::thread server_thread(RunServer, server_address);

    bool running = true;
    while (running) {
      char c = getchar();
      if (c == '\n' || c == EOF) continue;
      if (c == 'q') {
        // reset running flag and shutdown server
        running = false;
        server_ptr->Shutdown();
      }
    }
    server_thread.join();
    return 0; 


}

大家有可能看到了main函数,本人偷懒,将其一起写在一个文件里了,最好还是将main函数实现放到另外的文件。当然我们重点是将grpc的运用,大家可以借鉴一下里面server是怎样绑定IP和port口,运行起来server的。

生成一下,看看是不是和预想的一样啊?

3. 客户端代码

客户端代码主要是调用服务器端的接口,就是上面写的接口,grpc通过一个stub代理来实现,这样我们就象调用本地的函数一样去远程调用函数接口了,从而达到访问服务的目的。

客户端的cmakelist.txt和服务器端的有点类似,我贴出来,大家看看就行:

cmake_minimum_required(VERSION 3.20)

# Note: 8 target(s) were omitted.


message("--------" $ENV{VCPKG_ROOT})

set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")
project(CalculationInGrpcClient)

set(_GRPC_GRPCPP gRPC::grpc++)
set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf)
set(_REFLECTION gRPC::grpc++_reflection)

set(_PROTOBUF_LIBPROTOBUF_D libprotobufd)
find_package(gRPC CONFIG REQUIRED)
find_program(_PROTOBUF_PROTOC protoc REQUIRED)
find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin REQUIRED)

# Generated proto sources
get_filename_component(proto "../proto/calculation.proto" ABSOLUTE)
get_filename_component(proto_name "../proto/calculation.proto" NAME_WE)
get_filename_component(proto_path "${proto}" PATH)

set(proto_srcs "${proto_path}/${proto_name}.pb.cc")
set(proto_hdrs "${proto_path}/${proto_name}.pb.h")
set(grpc_srcs "${proto_path}/${proto_name}.grpc.pb.cc")
set(grpc_hdrs "${proto_path}/${proto_name}.grpc.pb.h")
message("------------------------------------------------")
message(${_PROTOBUF_PROTOC})
message(${_GRPC_CPP_PLUGIN_EXECUTABLE})
message(${proto_path})

message("-------------------------------------------------")
add_custom_command(
      OUTPUT "${proto_srcs}" "${proto_hdrs}" "${grpc_srcs}" "${grpc_hdrs}"
      COMMAND ${_PROTOBUF_PROTOC}
      ARGS --grpc_out "${proto_path}"
        --cpp_out "${proto_path}"
        -I "${proto_path}"
        --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
        "${proto}"
      DEPENDS "${proto}")

# Include generated *.pb.h files
include_directories(
    "${proto_path}"
    )

file(GLOB PUBLIC_HEADER ${CMAKE_CURRENT_BINARY_DIR}/*.h
                        ${PROJECT_SOURCE_DIR}/../proto/*.h)

add_executable(${PROJECT_NAME} CalculationClient.cc ${proto_srcs} ${grpc_srcs})
target_link_libraries(${PROJECT_NAME} PRIVATE gRPC::gpr gRPC::upb gRPC::grpc gRPC::grpc++)

message("protobuf libs are:")
message(${_PROTOBUF_LIBPROTOBUF_D})

 下面就是要介绍客户端的代码模块了,我这边简单封装了一个客户端类去调用服务,代码如下,

大家看看简单的request/reply调用方式。


#include <grpcpp/grpcpp.h>
#include <grpcpp/security/server_credentials.h>
#include <grpcpp/server.h>
#include <grpcpp/server_builder.h>
#include <grpcpp/server_context.h>

#include <calculation.grpc.pb.h>
#include <calculation.pb.h>
#include <thread>

using grpc::Server;
using grpc::ServerBuilder;
using ::grpc::ServerContext;
using grpc::ServerReader;
using grpc::ServerReaderWriter;
using grpc::ServerWriter;
using grpc::Status;

class CalculationInGrpcClient final {
public:
    CalculationInGrpcClient(CalculationInGrpcClient& param) = delete;
    CalculationInGrpcClient& operator=(CalculationInGrpcClient& param) = delete;

    CalculationInGrpcClient(std::shared_ptr<grpc::Channel> channelPtr);
    ~CalculationInGrpcClient(){};

    bool RequestAddOperation(const int32_t a, const int32_t b, int32_t& result);
    bool RequestMultiplyOperation(const int32_t a, const int32_t b, int32_t& result);

private:

    std::unique_ptr<data_handler::CalculationInterface::Stub> mStub;
};

CalculationInGrpcClient::CalculationInGrpcClient(
    std::shared_ptr<grpc::Channel> channel)
    : mStub(data_handler::CalculationInterface::NewStub(channel)) {}

bool CalculationInGrpcClient::RequestAddOperation(int32_t a, int32_t b,
                                                  int32_t& result) {
    grpc::Status grcpStatus;
    grpc::ClientContext context;
    data_handler::AddReply reply;
    data_handler::AddRequest request;
    request.set_param1(a);
    request.set_param2(b);
    grcpStatus = mStub->Add(&context, request, &reply);

    if (grcpStatus.error_code() == ::grpc::StatusCode::OK) {
        result = static_cast<int32_t>(reply.result());
        std::cout << "After adding operation, the result is: " 
                    << result
                    << std::endl;
        return true;
    } else {
        std::cout << "Server not running..." << std::endl;
    }
    return false;
}

bool CalculationInGrpcClient::RequestMultiplyOperation(int32_t a, int32_t b,
                                                       int32_t& result) {
    grpc::Status grcpStatus;
    grpc::ClientContext context;
    data_handler::MultiplyReply reply;
    data_handler::MultiplyRequest request;
    request.set_param1(a);
    request.set_param2(b);
    grcpStatus = mStub->Multiply(&context, request, &reply);

    if (grcpStatus.error_code() == ::grpc::StatusCode::OK) {
        result = static_cast<int32_t>(reply.result());
        std::cout << "After Multiplication operation, the result is: " 
                    << result
                    << std::endl;
        return true;
    } else {
        std::cout << "Server not running..." << std::endl;
    }
    return false;
}

void showHelp() {
    std::cout << "Calculation starts : \r\n\
            Press 'q' to exit the calculator. \r\n\
            "
            << std::endl;
}

bool FindParamters(const std::string& src, const char operation, int32_t& left, int32_t& right) {
  auto it = src.find(operation);
  if (it != std::string::npos) {
    std::string leftParam = src.substr(0, it);
    std::string rightParam = src.substr(it + 1, src.length() - it - 1);
    left = atoi(leftParam.c_str());
    right = atoi(rightParam.c_str());
    return true;
  }
  return false;
}

int main() { 

    showHelp();
    auto grpcChannel = grpc::CreateChannel("127.0.0.1:50051",
                                         grpc::InsecureChannelCredentials());
    if (!grpcChannel) {
        printf("Failed to create gRPC channel\n");
        return 0;
    }

    std::unique_ptr<CalculationInGrpcClient> clientPtr =
        std::make_unique<CalculationInGrpcClient>(grpcChannel);

    bool running = true;
    while (running) {
        std::string strTmp;
        std::getline(std::cin, strTmp);
        int32_t a = 0;
        int32_t b = 0;
        int32_t result = 0;
        if (FindParamters(strTmp, '+', a, b)) {
            if (clientPtr) {
            clientPtr->RequestAddOperation(a, b, result);
            }
        } else if (FindParamters(strTmp, '*', a, b)) {
            if (clientPtr) {
            clientPtr->RequestMultiplyOperation(a, b, result);
            }
        } else {
            // reserve
        }

        if (strTmp.find('q') != std::string::npos) {
            // reset running flag and shutdown server
            running = false;
        }
    }

    return 0; 


}

代码里面的request, reply基本上是固定格式:

    grpc::Status grcpStatus;
    grpc::ClientContext context;
    data_handler::MultiplyReply reply;
    data_handler::MultiplyRequest request;
    request.set_param1(a);
    request.set_param2(b);
    grcpStatus = mStub->Multiply(&context, request, &reply);

主要是stub去调用服务器端的接口,而前的context, request, reply都是准备工作。

grpc的createChannel绑定了服务器端的IP和port,进行服务器端和客户端通信,grpc都封装好了,固定格式调用就行。

4. 编译生成后,运行服务器端后,在运行客户端

 好了,就先到这里吧,代码只是demo,大家看看就行,里面有些不严谨的地方,多多担担!

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

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

相关文章

4月23日作业

#include <iostream> #include <cstring> using namespace std; class Student //学生类 { private: string name; //姓名 int year; //年龄 double sorce; //分数 public: Student (){} //无参构造 Student(string a,int b,double c):name(a),y…

元宇宙营销策略、玩法与案例

“元宇宙”依旧是当下品牌创新营销的重要形式&#xff0c;从时趣的行业观察来看&#xff0c;大量品牌方都有着元宇宙的营销意向&#xff0c;但在营销落地上存在不同的进度。一个显而易见的事实是&#xff0c;元宇宙不仅仅是一个虚拟的游戏空间&#xff0c;更是一个未来人人都会…

Java 抽象类和接口

一、抽象类和接口定义和使用场景 当你需要设计一些类&#xff0c;这些类有一些属性和方法是可以共享的&#xff0c;但同时又有一些属性和方法是需要不同的。在这种情况下&#xff0c;Java中提供了两种不同的机制&#xff0c;即“抽象类”和“接口”。 抽象类是一个类&#xff0…

第二十一章 光源

光源是每个场景必不可少的部分&#xff0c;光源除了能够照亮场景之外&#xff0c;还可以产生阴影效果。 Unity中分为四种光源类型&#xff1a; 1. 方向光&#xff1a;Directional Light 用于模拟太阳光&#xff0c;方向光任何地方都能照射到。 2. 点光源&#xff1a;Point L…

Java面试题总结 | Java面试题总结9- RabbitMQ模块(持续更新)

RabbitMQ 文章目录 RabbitMQ为什么使用Rabbitmq而不是其他的消息队列为什么使用消息队列解耦异步削峰 消息队列有什么优缺点MQ的高可用保障单机模式 普通集群模式&#xff08;无高可用性&#xff09;镜像集群模式&#xff08;高可用性&#xff09; MQ如何保证不重复消费、幂等性…

浏览器安全之XSS跨站脚本

基本概念 跨站脚本&#xff08;Cross-Site Scripting&#xff0c;XSS&#xff09;是一种经常出现在Web应用程序中的计算机安全漏洞&#xff0c;是由于Web应用程序对用户的输入过滤不足而产生的。 攻击者利用网站漏洞把恶意的脚本代码&#xff08;通常包括HTML代码和客户端Javas…

Vue.js 框架能力的官方认证

这两天Vue官方推出了Vue.js 认证计划。 即框架能力官方认证&#xff0c;即 Vue.js 框架能力证书。该认证由 Vue School 与 Vue.js 团队合作提供支持。官网&#xff1a;Vue.js - Official Certification Program 官方介绍此次的试题集和代码挑战由 Vue.js 核心团队审核&#xff…

react之按钮鉴权

使用HOC来完成 HOC&#xff1a;高阶组件&#xff0c;是React中复用组件逻辑的一种高级技巧。HOC自身不是React API的一部分&#xff0c;他是一种基于React的组合特性而形成的设计模式。 作用&#xff1a;用于复用组件的业务逻辑 VUE mixinReact Hoc 用户数据渲染带操作按钮渲…

Shell快速入门笔记

文章目录 Shell 快速入门笔记1、Shell概述2、Shell初体验4、注释5、变量6、数据类型6.1 字符串6.2 数组 7、参数传递8、运算符9、常用命令9.1 echo命令9.2 printf命令9.3 test命令 10、流程控制10.1 条件判断10.2 循环 11、函数12、输入/输出重定向12.0 前置知识12.1 输出重定向…

IPsec中IKE与ISAKMP过程分析(主模式-消息3)

IPsec中IKE与ISAKMP过程分析&#xff08;主模式-消息1&#xff09;_搞搞搞高傲的博客-CSDN博客 IPsec中IKE与ISAKMP过程分析&#xff08;主模式-消息2&#xff09;_搞搞搞高傲的博客-CSDN博客 阶段目标过程消息IKE第一阶段建立一个ISAKMP SA实现通信双发的身份鉴别和密钥交换&…

一文详细介绍查看和启用nginx日志(access.log和error.log),nginx错误日志的安全级别,自定义访问日志中的格式

文章目录 1. 文章引言2. Nginx访问日志(access.log)2.1 简述访问日志2.2 启用Nginx访问日志2.3 自定义访问日志中的格式 3. Nginx错误日志(error.log)3.1 简述错误日志3.2 启用错误日志3.3 Nginx错误日志的安全级别 4. 文末总结 1. 文章引言 我们在实际工作中&#xff0c;经常使…

数字设计小思 - D触发器与死缠烂打的亚稳态

前言 本系列整理数字系统设计的相关知识体系架构&#xff0c;为了方便后续自己查阅与求职准备。在FPGA和ASIC设计中&#xff0c;D触发器是最常用的器件&#xff0c;也可以说是时序逻辑的核心&#xff0c;本文根据个人的思考历程结合相关书籍内容和网上文章&#xff0c;聊一聊D…

函数-函数递归及练习

目录 1、什么是递归&#xff1f; 2、递归的两个必要条件 3、递归的练习 3.1 接受一个整型值&#xff08;无符号&#xff09;&#xff0c;按照顺序打印它的每一位 3.2 编写函数不允许创建临时变量&#xff0c;求字符串的长度 3.3 求第n个斐波那契数 3.4 字符串逆序&…

Go语言-数据结构与算法

go语言之专业数据结构与算法 20.4 稀疏 sparsearray 数组 20.4.1 先看一个实际的需求  编写的五子棋程序中&#xff0c;有存盘退出和续上盘的功能 稀疏数组的处理方法是 : 1) 记录数组一共有几行几列&#xff0c;有多少个不同的值 2) 思想&#xff1a;把具有不同值…

【五一创作】【Midjourney】Midjourney 连续性人物创作 ② ( 获取大图和 Seed 随机种子 | 通过 seed 随机种子生成类似图像 )

文章目录 一、获取大图和 Seed 随机种子二、通过 seed 种子生成类似图像 一、获取大图和 Seed 随机种子 注意 : 一定是使用 U 按钮 , 在生成的大图的基础上 , 添加 信封 表情 , 才能获取该大图的 Seed 种子编码 ; 在上一篇博客生成图像的基础上 , 点击 U3 获取第三张图的大图 ;…

STL常用梳理——VECTOR常用接口及其迭代器实现

Vector篇 Vector介绍Vector实现1、定义默认构造函数使用实现 2、迭代器Iterator迭代器使用 3、空间增长问题使用实现 迭代器迭代器介绍迭代器实现 Vector介绍 vector是STL中容器之一&#xff0c;特性如下&#xff1a; vector是表示可变大小数组的序列容器。就像数组一样&#…

Python基础合集 练习21 (错误与异常处理语句)

‘’‘try: block1 except[ExceptionName]: block2 ‘’’ block1:执行代码,表示可能会出现错误的代码块 ExceptionName: 表示要捕获的异常名称,为可选参数.如果不指定异常名称,则表示捕获所有异常 block2:表示发生异常时执行的代码块 while True: try: num int(input(请输…

设计模式——工厂模式

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线设计模式牛客面试题 目录 1、工厂模式介绍 2、披萨项目需求 3、传统方式 4、非静态简单工厂模式 5、静态简单工厂模式 6、工厂方法模式 7、抽象工厂模…

spass modeler

课时1&#xff1a;SPSS Modeler 简介 本课时一共分为五个模块&#xff0c;分别是Modeler概述、工具安装、窗口说明以及功能介绍和应用案例。相信通过本课时内容的学习&#xff0c;大家将会对SPSS Modeler有个基础的了解. 在学习本节课内容之前&#xff0c;先来看看本节课我们究…

目标检测模型量化---用POT工具实现YOLOv5模型INT8量化

POT工具是什么 POT工具&#xff0c;全称&#xff1a;Post-training Optimization Tool&#xff0c;即训练后优化工具&#xff0c;主要功能是将YOLOv5 OpenVINO™ FP32 模型进行 INT8 量化&#xff0c;实现模型文件压缩&#xff0c;从而进一步提高模型推理性能。 不同于 Quantiz…