我遇到了两个令人沮丧的问题,它来自于想要从nix-crate使用sendmmsg在Unix套接字上发送消息。
我有一些给定的消息,可能包含也可能不包含FDS。NIX做的大多数事情都是零拷贝的,这使得它有时很难使用,这让你不得不与借用检查器和类型系统作斗争,这两个问题都来自这个函数签名:
pub fn sendmmsg<'a, I, C, S>(
fd: RawFd,
data: impl std::iter::IntoIterator<Item=&'a SendMmsgData<'a, I, C, S>>,
flags: MsgFlags
) -> Result<Vec<usize>>
where
I: AsRef<[IoSlice<'a>]> + 'a,
C: AsRef<[ControlMessage<'a>]> + 'a,
S: SockaddrLike + 'a
其中,SendMmsgData定义为:
pub struct SendMmsgData<'a, I, C, S>
where
I: AsRef<[IoSlice<'a>]>,
C: AsRef<[ControlMessage<'a>]>,
S: SockaddrLike + 'a
{
pub iov: I,
pub cmsgs: C,
pub addr: Option<S>,
pub _lt: std::marker::PhantomData<&'a I>,
}
这是与之连接的代码
...
# [inline]
fn exec_write_many<M>(&mut self, messages: Vec<M>) -> Result<usize, Error>
where
M: SocketMessage,
{
let mut sent = 0;
let mut send_message_data = vec![];
for msg in messages.iter() {
let mmsg_data = if msg.borrow_fds().is_empty() {
SendMmsgData {
iov: [IoSlice::new(msg.borrow_buf()); 1],
cmsgs: vec![],
addr: NONE_ADDR,
_lt: std::marker::PhantomData::default(),
}
} else {
SendMmsgData {
iov: [IoSlice::new(msg.borrow_buf()); 1],
cmsgs: vec![ControlMessage::ScmRights(msg.borrow_fds())],
addr: NONE_ADDR,
_lt: std::marker::PhantomData::default(),
}
};
send_message_data.push(mmsg_data);
}
match nix::sys::socket::sendmmsg(self.sock_fd, &send_message_data, MsgFlags::MSG_DONTWAIT) {
...
这两个问题都是可控的,但都是以性能为代价的,从主要的问题开始:我想为sendmmsg提供一个以如下方式创建的迭代器:
...
# [inline]
fn exec_write_many<M>(&mut self, messages: Vec<M>) -> Result<usize, Error>
where
M: SocketMessage,
{
let mut sent = 0;
let sendmmsgs = messages.iter()
.map(|msg| {
if msg.borrow_fds().is_empty() {
SendMmsgData {
iov: [IoSlice::new(msg.borrow_buf()); 1],
cmsgs: vec![],
addr: NONE_ADDR,
_lt: std::marker::PhantomData::default(),
}
} else {
SendMmsgData {
iov: [IoSlice::new(msg.borrow_buf()); 1],
cmsgs: vec![ControlMessage::ScmRights(msg.borrow_fds())],
addr: NONE_ADDR,
_lt: std::marker::PhantomData::default(),
}
}
});
match nix::sys::socket::sendmmsg(self.sock_fd, sendmmsgs, MsgFlags::MSG_DONTWAIT) {
...
但是,由于SendMmsgData归迭代器所有,因此我得到了如下结果:
error[E0271]: type mismatch resolving `<[closure@socks/src/buffered_writer.rs:146:18: 162:14] as FnOnce<(&M,)>>::Output == &SendMmsgData<'_, _, _, _>`
--> socks/src/buffered_writer.rs:163:56
|
163 | match nix::sys::socket::sendmmsg(self.sock_fd, sendmmsgs, MsgFlags::MSG_DONTWAIT) {
| -------------------------- ^^^^^^^^^ expected reference, found struct `SendMmsgData`
| |
| required by a bound introduced by this call
|
= note: expected reference `&SendMmsgData<'_, _, _, _>`
found struct `SendMmsgData<'_, [IoSlice<'_>; 1], Vec<ControlMessage<'_>>, ()>`
= note: required because of the requirements on the impl of `Iterator` for `Map<std::slice::Iter<'_, M>, [closure@socks/src/buffered_writer.rs:146:18: 162:14]>`
note: required by a bound in `nix::sys::socket::sendmmsg`
--> /home/gramar/.cargo/registry/src/github.com-1ecc6299db9ec823/nix-0.24.2/src/sys/socket/mod.rs:1456:40
|
1456 | data: impl std::iter::IntoIterator<Item=&'a SendMmsgData<'a, I, C, S>>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nix::sys::socket::sendmmsg`
例如,对于Options
,我可以只调用as_ref()
将内部的T
转换为&T
,但我不知道如何使用迭代器来实现这一点,因为迭代器让我分配另一个向量并循环要发送的所有消息。
第二个较小的问题是cmsgs
。类型系统不允许使用数组,因为一个分支的类型为[_ ;1]
,另一个分支的类型为[_ ;0]
。空的VEC不会导致分配,而具有一个项目的VEC将会导致分配。
这两个问题都让我遇到了同样的问题。我不知道如何创建 Package 器结构并分别实现IntoIterator<Item=&'a SendMmsgData<'a, I, C, S>>
和AsRef<[ControlMessage<'a>]>
,因为这两个都需要我返回将在函数体中创建的引用,因为我的数据结构不是SendMmsgData
或ControlMessage
的形式,而且这两个引用都引用了其他一些内存,在我的例子中,我必须创建一个具有自己的缓冲区和对该结构的内部引用(对self的内部引用)的结构,这会产生其他问题。
你知道我如何才能在没有额外循环/分配的情况下做到这一点吗?
PS。在测量方面,由于系统调用的工作方式,当消息没有FDS时,这样做会造成大约10%的性能影响,而在只有消息具有FDS的情况下,性能会提高2%:
if msg.borrow_fds().is_empty() {
SendMmsgData {
iov: [IoSlice::new(msg.borrow_buf()); 1],
cmsgs: [ControlMessage::ScmRights(&[])],
addr: NONE_ADDR,
_lt: std::marker::PhantomData::default(),
}
} else {
SendMmsgData {
iov: [IoSlice::new(msg.borrow_buf()); 1],
cmsgs: [ControlMessage::ScmRights(msg.borrow_fds())],
addr: NONE_ADDR,
_lt: std::marker::PhantomData::default(),
}
};
我可以直接使用libc板条箱,如果我不能以更好的方式解决这个问题,我将不得不这样做。
2条答案
按热度按时间k97glaaz1#
第一个问题的问题是,
sendmmsg
可能会在迭代器n
上多次调用next
,从而获得对n``SendMsgData
的引用,这些引用都必须位于某个地方。因为你不知道n
是什么,所以所有SendMsgData
都必须住在某个地方,所以你必须在Vec
中缓冲它们。这可以通过将sendmmsg
的API更改为接受拥有或借用的SendMsgData
s来修复,但您显然无法控制这一点。不过,我认为
cmsgs
问题是可以帮助解决的。您可以创建自己的类似Option
的 Package 器,它完全驻留在堆栈上,并根据它是否包含值来实现AsRef
:gkl3eglg2#
就像公认的答案所说的那样,您的临时结构应该位于某个地方。在我看来,您是按值接受输入参数的,所以您可能可以修改它们。是的,我提出了一些“肮脏”的解决方案,即使对我来说也不是很好,但当性能真的很重要时,我们可以尝试一下。
因此,我们的想法是将
SendMmsgData
结构作为Option<SendMmsgData>
放入消息参数中,并使SocketMessage
特征具有fn get_send_mmsg_data(&mut self) -> &SendMmsgData
。Here is example code
是的,最好是把PR变成Rust STD库,让它能接受参考文献。