使用通道在线程之间通信Rust pnet数据包

时间:2018-12-07 19:58:16

标签: multithreading rust pcap

我正在研究一个简单的Rust程序,该程序可以读取和解析网络数据包。为了读取网络数据包,我使用了pnet libary。 因为解析可能要花一些时间,所以我使用两个单独的线程来读取和解析数据包。

我现在的想法是通过消息传递(使用mpsc::channel())将读取的包从第一个线程传递到第二个线程。 这是我根据the example given in the pnet doc编写的代码的简化版本:

extern crate pnet;

use std::sync::mpsc;
use std::thread;

use pnet::datalink;
use pnet::datalink::Channel::Ethernet;

fn main() {
    let (sender, receiver) = mpsc::channel();
    thread::spawn(move || {
        for packet in receiver.recv() {
            println!("{:?}", packet)
        }
    });

    let interface = datalink::interfaces().into_iter()
        .find(|interface| interface.name == "enp5s0")
        .unwrap();
    let (_, mut package_receiver) =
        match datalink::channel(&interface, Default::default()) {
            Ok(Ethernet(tx, rx)) => (tx, rx),
            _ => panic!()
        };

    loop {
        match package_receiver.next() {
            Ok(packet) => {
                // sender.send("foo"); // this works fine
                sender.send(packet);
            }
            _ => panic!()
        }
    }
}

这对于通过通道发送基本类型或字符串有效,但不适用于网络数据包。当我尝试通过通道将数据包发送到解析器线程时,出现以下编译器错误:

error[E0597]: `*package_receiver` does not live long enough                                                                   
--> src/main.rs:28:15                                                                                                       
|                                                                                                                          
28 |         match package_receiver.next() {                                                                                  
|               ^^^^^^^^^^^^^^^^ borrowed value does not live long enough                                                  
...                                                                                                                           
36 | }                                                                                                                        
| - borrowed value only lives until here                                                                                   
|                                                                                                                          
= note: borrowed value must be valid for the static lifetime...

我对Rust还是很陌生,非常感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

packet&[u8]类型,其生存期为'a,也与package_receiver调用中对next()的引用的生存期相同。具有生存期的next()定义如下:

fn next(&'a mut self) -> Result<&'a [u8]>

send &[u8]到一个线程。但是线程可能会超过您发送给它的引用的寿命,从而导致引用悬空。结果,编译器抱怨他们需要具有'static的生存期。"foo"可以工作,因为它是&'static str

一种方法是获取数据的所有权,然后将其作为值发送到另一个线程。

Ok(packet) => {
   // sender.send("foo"); // this works fine
   sender.send(packet.to_owned());
}

您还可以看看如何将作用域线程与crossbeam一起使用

答案 1 :(得分:0)

package_receiver.next()调用定义为:

pub trait DataLinkReceiver: Send {
    fn next(&mut self) -> Result<&[u8]>;
}

mspc :: Sender将发送定义为:

pub fn send(&self, t: T) -> Result<(), SendError<T>>

所以package_receiver.next()返回一个结果,该结果包含对字节片&[u8] 的引用。因此,当您随后调用 sender.send(packet); 时,这意味着您要将引用发送给另一个线程。但是,package_receiver.next()的匹配范围不能保证引用的寿命比范围的结尾更长。因此,不能保证另一个线程在访问该数据时该引用仍然有效。

str之所以有效,是因为它是静态的生存期字符串。不管什么线程读取该内存,该内存始终有效。

如果您将呼叫更改为:

sender.send(Vec::from(packet))

这将创建一个Vec变量,将数据包切片复制到新的内存中,然后将该变量的所有权传递给另一个线程。这保证了另一个接收线程将始终可以清晰地访问该数据。由于所有权是明确传递的,因此编译器将接收线程中的代码称为接收的Vec变量的生命周期结束的地方。

使用.send()的结果时还会出现一些其他错误,可以通过以下方式解决:

if sender.send(Vec::from(packet)).is_err() {
    println!("Send error");
}