我正在将低延迟实时应用程序移植到 Rust。本质上我想在不复制的情况下执行尽可能多的操作。目前内存分配的主要来源来自网络堆栈。每次接收或发送数据包时,都会在幕后进行大量复制,以强制从 Rust 结构中检索字节。我想停止这样做。
本质上我希望能够将数据包从字节转换为字节并传输它。下面是一个无法正常工作的示例代码,它说明了我想要做的事情。
#[repr(C, align(4))]
#[derive(Debug)]
struct Content {
first: u8,
padding: [u8; 3]
}
#[repr(C, align(4))]
#[derive(Debug)]
struct Packet {
header_id: u16,
header_padding: [u8; 2],
// This is another pointer I want it to be located after header_padding
content: Box<[Content]>,
}
#[derive(Debug)]
struct ParsedPacket {
content_length: usize,
// The box below should directly reference the original u8 bytes without copying
packet: Box<Packet>,
}
unsafe fn buf_to_packet(bs: Box<[u8]>) -> Option<Box<ParsedPacket>> {
// How to implement this
// First 4 bytes are the header
// The rest must be 4 byte multiple and each 4 byte is a Content struct
// So from the length of the boxed slice we can infer the content_length
todo!()
}
unsafe fn packet_to_buf(bs: ParsedPacket) -> Box<[u8]> {
todo!()
}
fn main() {
let bytes = vec![
// Header
0, 23, 0, 0,
// Content 0
0, 0, 0, 0,
// Content 1
1, 0, 0, 0,
// Content 2
0, 0, 0, 0
].into_boxed_slice();
let packet = unsafe { buf_to_packet(bytes) };
println!("{:?}", content);
}
这有两个问题。 Rust 要求内容是一个装箱切片。它的主干不一定位于结构本身,但可能位于其他地方。此外,盒装切片还包含其自己的长度簿记。我想我需要自己对长度进行簿记。但我不知道如何在不使用盒装切片的情况下添加具有动态长度的结构成员。
我当然愿意用unsafe来做这件事。但我对 Rust 没有经验,所以我不知道如何实际实现它。
本质上我在 Rust 中寻找的是来自 C 的灵活的数组成员。
您可以向结构添加切片以将数据直接放入结构中:
struct MyDST {
variable_length: [u8],
}
因为使用
Box
需要 Alignment
和
[u8]
的 [Content]
相同,但它们不是你不能使用盒子。下面的代码使用引用代替。
#![feature(ptr_metadata, layout_for_ptr, pointer_is_aligned)]
#[repr(C, align(4))]
#[derive(Debug)]
struct Content {
first: u8,
padding: [u8; 3],
}
#[repr(C, align(4))]
#[derive(Debug)]
struct Packet {
header_id: u16,
header_padding: [u8; 2],
content: [Content],
}
fn buf_to_packet(bs: &[u8]) -> Option<&Packet> {
const PACKET_HEADER_LEN: usize = 4;
#[cfg(debug_assertions)]
{
let val: *const Packet = std::ptr::from_raw_parts(std::ptr::null(), 0);
// SAFETY:
// 0 is an initialized length that fits in an isize.
assert_eq!(PACKET_HEADER_LEN, unsafe { std::mem::size_of_val_raw(val) });
}
if bs.len() < PACKET_HEADER_LEN
|| (bs.len() - PACKET_HEADER_LEN) % std::mem::size_of::<Content>() != 0
{
return None;
}
let (ptr, len): (*const (), usize) = (bs as *const [u8]).to_raw_parts();
let packet: *const Packet = std::ptr::from_raw_parts(
ptr,
(len - PACKET_HEADER_LEN) / std::mem::size_of::<Content>(),
);
// SAFETY:
// - The pointer is correctly aligned to a multiple of 4
// - all bit patterns are valid and the pointer is initialized
packet.is_aligned_to(4).then_some(unsafe { &* packet })
}
fn main() {
let bytes = vec![
23, 0, 0, 0, // Header
0, 0xca, 0xfc, 0xe2, // Content 0
1, 0, 0, 0, // Content 1
2, 0, 0, 0, // Content 2
]
.into_boxed_slice();
let packet = buf_to_packet(&*bytes);
println!("{:?}", packet);
}