折腾笔记[20]-使用rust交换protobuf数据
摘要
使用rust和protobuf3交换数字数据.
关键词
rust;protobuf;
关键信息
[dependencies]
# protobuf解析
prost = { version = "0.13.5", features = ["prost-derive"] }
# gRPC通信
tonic = { version = "0.13.0", features = ["prost"] }
# build专用依赖
[build-dependencies]
tonic-build = "0.8.0"
原理简介
protobuf3简介
[https://protobuf.com.cn/programming-guides/proto3/]
[https://protobuf.com.cn/overview/]
Protocol buffers 是一种语言中立、平台中立、可扩展的结构化数据序列化机制。
它类似于 JSON,但更小、更快,并且可以生成原生语言绑定。您可以定义一次数据结构,然后使用特殊的生成源代码,轻松地将结构化数据写入和读取到各种数据流,并使用各种语言。
Protocol buffers 是定义语言(在 .proto 文件中创建)、proto 编译器生成的用于与数据交互的代码、特定于语言的运行时库、写入文件(或通过网络连接发送)的数据的序列化格式以及序列化数据的组合。
Protocol buffers 为类型化、结构化数据包提供了一种序列化格式,这些数据包的大小可达几兆字节。该格式既适用于临时网络流量,也适用于长期数据存储。Protocol buffers 可以扩展新信息,而不会使现有数据失效或需要更新代码。
Protocol buffers 是 Google 最常用的数据格式。它们广泛用于服务器间通信以及磁盘上数据的归档存储。Protocol buffer 消息 和 服务 由工程师编写的 .proto 文件描述。
DDS对比gRPC
gRPC和DDS(数据分发服务)都是用于分布式系统通信的技术,但它们在架构、通信模型、性能和适用场景等方面存在显著差异。以下是两者的详细对比:
通信模型
- gRPC:基于客户端-服务器架构,采用请求-响应模式。客户端发起请求,服务器处理请求并返回响应。
- DDS:基于发布-订阅模型,采用点对点的对等网络架构。每个节点既可以发布数据,也可以订阅数据,无需中心服务器。
性能
- gRPC:
- 使用HTTP/2协议,支持多路复用、持久连接和高效的二进制数据传输,性能优于传统的REST。
- 在处理小数据量时性能表现优异,但在传输大二进制数据时,单次传输(Unary)方式可能不如流式传输(Streaming)。
 
- DDS:
- 性能与“原生”RPC实现相当,在某些场景下甚至比Web服务快数十倍。
- 由于其对等架构,数据传输路径更短,适合低延迟和高吞吐量的场景。
 
可靠性和容错性
- gRPC:由于依赖中心服务器,可能存在单点故障风险。不过,通过负载均衡和冗余部署可以缓解这一问题。
- DDS:对等架构使其具有更好的容错性。即使部分节点失效,其他节点仍可继续通信。
开发复杂度
- gRPC:需要定义接口并生成代码,开发和维护相对复杂。但其强类型接口和代码生成工具可以减少错误。
- DDS:支持自动代码生成,开发过程较为简化。
适用场景
- gRPC:
- 适用于对性能要求较高的内部服务通信,如实时应用、微服务架构中的服务间通信。
- 适合需要强类型接口和自动代码生成的场景。
 
- DDS:
- 广泛应用于对可靠性、低延迟和高吞吐量要求极高的领域,如航空航天、工业自动化和物联网。
- 适合需要高度解耦和动态通信的场景。
 
总结
- 如果你的系统需要高性能、低延迟的内部通信,且对开发复杂度和维护成本可以接受,可以选择gRPC。
- 如果你的系统需要高度解耦、动态通信和容错能力,且对性能要求极高,可以选择DDS。
实现
[https://rust-book.junmajinlong.com/ch101/02_Protobuf_tonic.html]
build.rs
#![allow(unused)]
//! 预处理protobuf文件为rust文件
// 标准库
use std::io::Result;
// 主函数
fn main() -> Result<()> {
    // 转换proto文件为rust文件
    tonic_build::configure()
            // 是否编译生成用于服务端的代码
            .build_server(true) 
            // 是否编译生成用于客户端的代码
            .build_client(true) 
            // 输出的路径,此处指定为项目根目录下的assets目录
            .out_dir("assets")  
            // 指定要编译的proto文件路径列表,第二个参数是提供protobuf的扩展路径,
            // 因为protobuf官方提供了一些扩展功能,自己也可能会写一些扩展功能,
            // 如存在,则指定扩展文件路径,如果没有,则指定为proto文件所在目录即可
            .compile(&["./assets/num.proto"], &["protos"])?; 
    Ok(())
}
main.rs
#![allow(unused)]
//! 使用protobuf解析消息文件num.proto为rust代码并测试相互通信
// 导入proto生成的rust文件
pub mod num {
    include!("../../assets/num.rs");
}
use num::Number;
use prost::Message;
fn main() {
    // 构造一个数字消息
    let number = Number { num: 42 };
    // 将数字消息序列化为字节数组
    let serialized = serialize_number(&number).unwrap();
    println!("序列化后的字节数组: {:?}", serialized);
    // 将字节数组反序列化为数字消息
    let deserialized = deserialize_number(&serialized).unwrap();
    println!("反序列化后的数字消息: {:?}", deserialized.num);
}
/// 将Number消息序列化为字节数组
/// 
/// 参数:
/// - number: 要序列化的Number消息
/// 
/// 返回:
/// - 序列化后的字节数组,如果失败则返回错误
fn serialize_number(number: &Number) -> Result<Vec<u8>, prost::EncodeError> {
    // 使用prost的Message trait的encode方法进行序列化
    let mut buf = Vec::new();
    number.encode(&mut buf)?;
    Ok(buf)
}
/// 将字节数组反序列化为Number消息
/// 
/// 参数:
/// - buf: 要反序列化的字节数组
/// 
/// 返回:
/// - 反序列化后的Number消息,如果失败则返回错误
fn deserialize_number(buf: &[u8]) -> Result<Number, prost::DecodeError> {
    // 使用prost的Message trait的decode方法进行反序列化
    Number::decode(buf)
}
num.rs
/// 数字消息
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Number {
    #[prost(int64, tag = "1")]
    pub num: i64,
}
num.proto
syntax = "proto3";
// 数字消息
message Number {
  int64 num = 1;
}
效果
序列化后的字节数组: [8, 42]
反序列化后的数字消息: 42
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号