#binary-data #read-write #data-stream #reading #panic-free #varint #utility

binary-util

一个无panic的二进制工具包,用于在网络上读写二进制流

5个版本

0.3.4 2023年8月13日
0.3.3 2023年8月13日
0.3.2 2023年5月2日
0.3.1 2023年5月2日
0.3.0 2023年5月1日

269数据结构

每月下载量 34
rak-rs 中使用

Apache-2.0

84KB
1.5K SLoC

binary-util

一个无panic的二进制工具包,用于在网络上读写二进制流。

BinaryUtils提供以下功能

入门指南

Binary Utils可在 crates.io 上找到,请在您的 Cargo.toml 中添加以下内容

[dependencies]
binary_util = "0.3.4"

可选地,如果您想移除 derive 功能,您可以在您的 Cargo.toml 中添加以下内容

[dependencies]
binary_util = { version = "0.3.4", default-features = false }

要显式启用 derive,您可以使用

[dependencies]
binary_util = { version = "0.3.0", default-features = false, features = ["derive"] }

二进制IO

《io》模块提供了一种连续读写二进制数据的方式,同时确保无panic。该模块提供了两个结构体,分别是ByteReaderByteWriter,它们分别是对bytes::Bufbytes::BufMut的封装。通常情况下,当您需要手动读写二进制数据时,会使用ByteReaderByteWriter

读取示例:以下示例展示了如何从流中读取一个varint。

use binary_util::io::ByteReader;
const BUFFER: &[u8] = &[255, 255, 255, 255, 7]; // 2147483647
fn main() {
    let mut buf = ByteReader::from(&BUFFER[..]);
    buf.read_var_u32().unwrap();
}

写入示例:以下示例展示了如何将字符串写入流。

use binary_util::io::ByteWriter;
fn main() {
    let mut buf = ByteWriter::new();
    buf.write_string("Hello world!");
}

实际案例:此模块的一个更实际的应用案例可能是一个简单的pong服务器,其中包含两个数据包,分别是PingPong,它们通过UDP分别转发。以下示例使用ByteReaderByteWriter,利用std::net::UdpSocket发送和接收数据包。

use binary_util::io::{ByteReader, ByteWriter};
use std::net::UdpSocket;
pub struct PingPacket {
    pub time: u64
}
pub struct PongPacket {
    pub time: u64,
    pub ping_time: u64
}
fn main() -> std::io::Result<()> {
    let socket = UdpSocket::bind("127.0.0.1:5000")?;
    let mut buf = [0; 1024];
    loop {
        let (amt, src) = socket.recv_from(&mut buf)?;
        let mut buf = ByteReader::from(&buf[..amt]);
        match buf.read_u8()? {
            0 => {
                let ping = PingPacket {
                    time: buf.read_var_u64()?
                };
                println!("Received ping from {}", src);
                let mut writer = ByteWriter::new();
                let pong = PongPacket {
                    time: std::time::SystemTime::now()
                            .duration_since(
                                std::time::UNIX_EPOCH
                            )
                            .unwrap()
                            .as_millis() as u64,
                    ping_time: ping.time
                };
                // Write pong packet
                writer.write_u8(1);
                writer.write_var_u64(pong.time);
                writer.write_var_u64(pong.ping_time);
                socket.send_to(writer.as_slice(), src)?;
            },
            1 => {
                let pong = PongPacket {
                    time: buf.read_var_u64()?,
                    ping_time: buf.read_var_u64()?
                };
                println!(
                    "Received pong from {} with ping time of {}ms",
                    src,
                    pong.time - pong.ping_time
                );
            }
            _ => {
                println!("Received unknown packet from {}", src);
            }
        }
    }
}

接口

《interfaces》模块提供了一种使用两个特质,ReaderWriter,来实现读写二进制数据的方法。通常情况下,在实现枚举或结构体时,会使用BinaryIo;但在您需要实现一个可能不兼容BinaryIo的类型时,可以使用这些特质。

示例:以下示例为HelloPacket实现了ReaderWriter特质,允许它与BinaryIo一起使用;此示例还允许您使用更简单的约定读写数据包。

use binary_util::interfaces::{Reader, Writer};
use binary_util::io::{ByteReader, ByteWriter};
pub struct HelloPacket {
    pub name: String,
    pub age: u8,
    pub is_cool: bool,
    pub friends: Vec<String>
}
impl Reader<HelloPacket> for HelloPacket {
    fn read(buf: &mut ByteReader) -> std::io::Result<Self> {
        Ok(Self {
            name: buf.read_string()?,
            age: buf.read_u8()?,
            is_cool: buf.read_bool()?,
            friends: Vec::<String>::read(buf)?
        })
    }
}
impl Writer<HelloPacket> for HelloPacket {
    fn write(&self, buf: &mut ByteWriter) -> std::io::Result<()> {
        buf.write_string(&self.name);
        buf.write_u8(self.age);
        buf.write_bool(self.is_cool);
        self.friends.write(buf)?;
        Ok(())
    }
}

通过上述示例,现在您可以使用BinaryIo读取和写入数据包,同时还可以使用现在已实现的readwrite方法更轻松地进行读写。

fn main() {
    let mut buf = ByteWriter::new();
    let packet = HelloPacket {
        name: "John".to_string(),
        age: 18,
        is_cool: true,
        friends: vec!["Bob".to_string(), "Joe".to_string()]
    };
    buf.write_type(&packet).unwrap();
}

类型

《types》模块提供了一种使用BinaryIo派生宏实现非原始类型的方法。

本模块提供了以下辅助类型

通用用法

use binary_util::BinaryIo;
use binary_util::io::{ByteReader, ByteWriter};
use binary_util::types::{varu64, varu32, u24, i24, LE, BE};

#[derive(BinaryIo)]
pub struct ProxyStatusPacket {
   pub clients: u24,
   pub max_clients: u24,
   pub net_download: varu32,
   pub net_upload: varu64,
}

fn main() {
   let mut buf = ByteWriter::new();
   let packet = ProxyStatusPacket {
       clients: 10,
       max_clients: 100,
       net_download: 1000.into(),
       net_upload: 1000.into()
   };

   buf.write_type(&packet).unwrap();
   let mut buf = ByteReader::from(buf.as_slice());
   let packet = ProxyStatusPacket::read(&mut buf).unwrap();
   println!("Clients: {}", packet.clients);
   println!("Max Clients: {}", packet.max_clients);
   println!("Net Download: {}", packet.net_download.0);
   println!("Net Upload: {}", packet.net_upload.0);
}

依赖项

~2.5–4MB
~74K SLoC