Rust 如何使用东京';s UdpSocket在1服务器:N客户端设置中处理消息?

Rust 如何使用东京';s UdpSocket在1服务器:N客户端设置中处理消息?,rust,udp,closures,future,rust-tokio,Rust,Udp,Closures,Future,Rust Tokio,我想做什么: 。。。编写一个(1)服务器/(N)客户端(网络游戏-)体系结构,使用UDP套接字作为通信的基础 消息以Vec的形式发送,通过bincode(板条箱)编码 我还希望能够偶尔发送超过~1500字节的典型最大MTU的数据报,并在接收端正确组装,包括发送ack-消息等(我想我必须自己实现,对吗? 对于UdpSocket,我考虑使用tokio的实现,可能是framed。不过,我不确定这是否是一个好的选择,因为这似乎会引入一个不必要的步骤,将Vec(由bincode序列化)映射到Vec(东

我想做什么:

。。。编写一个(1)服务器/(N)客户端(网络游戏-)体系结构,使用UDP套接字作为通信的基础

  • 消息以
    Vec
    的形式发送,通过
    bincode
    (板条箱)编码

  • 我还希望能够偶尔发送超过~
    1500字节的典型最大
    MTU
    的数据报,并在接收端正确组装,包括发送
    ack
    -消息等(我想我必须自己实现,对吗?

对于
UdpSocket
,我考虑使用
tokio
的实现,可能是
framed
。不过,我不确定这是否是一个好的选择,因为这似乎会引入一个不必要的步骤,将
Vec
(由
bincode
序列化)映射到
Vec
(东京
UdpCodec
需要)(?)

考虑以下最小代码示例:

Cargo.toml(服务器)

Serde
Serde-derivate
用于定义协议的
共享
板条箱!)

(我想尽快用
tokio

fn main()->(){
让addr=format!(“127.0.0.1:{port}”,port=8080);
让addr=addr.parse::().expect(&format!(“无法从{}”,addr)中创建有效的SocketAddress”);
让mut core=core::new().unwrap();
让handle=core.handle();
让socket=UdpSocket::bind(&addr,&handle).expect(&format!(“无法将套接字绑定到地址{},addr));
让udp_future=socket.framed(MyCodec{}).for_each(|(addr,data)|{
socket.send_to(&data,&addr);//只需回显数据
好(())
});
core.run(udp_future.unwrap();
}
结构MyCodec;
为MyCodec植入UdpCodec{
输入=(SocketAddr,Vec);
输入输出=(SocketAddr,Vec);
fn解码(&mut self,src:&SocketAddr,buf:&u8])->io::结果{
Ok((*src,buf.to_vec())
}
fn编码(&mut self,msg:self::Out,buf:&mut Vec)->SocketAddr{
let(addr,mut data)=msg;
buf.追加(&mut数据);
地址
}
}
这里的问题是:

让udp_future=socket.framed(MyCodec{}).for_each(|(addr,data)|{ |----在此处移动的值^^^^^^^^^^^^^移动后在此处捕获的值 | =注意:发生移动是因为
套接字
具有类型
tokio_core::net::UdpSocket
,而该类型不实现
复制
特性

这个错误完全有道理,但我不确定如何创建这样一个简单的回显服务。事实上,消息的处理涉及到更多的ofc逻辑,但为了一个简单的例子,这应该足以给出一个粗略的想法

我的解决方法是一个丑陋的黑客:创建第二个套接字。

以下是Tokio文档中的签名:

pub fn framed<C: UdpCodec>(self, codec: C) -> UdpFramed<C>
我没有检查这段代码,因为(正如Shepmaster正确指出的)您的代码片段还有其他问题,但它应该会告诉您答案。我将重复前面的警告:如果您在真实代码中执行此操作,它将破坏您使用的网络协议。
get\u ref
的文档如下所示:

请注意,应注意不要篡改传入的底层数据流,因为它可能会损坏正在处理的帧流


回答您问题的新部分:是的,您需要自己处理重新组装,这意味着您的编解码器确实需要对您发送的字节进行一些帧处理。通常这可能涉及一个不能出现在
Vec
中的开始序列。开始序列允许您识别打包后下一条消息的开始et丢失(UDP经常发生这种情况)。如果在
Vec
中没有不能出现的字节序列,那么当它出现时,您需要对其进行转义。然后,您可以发送消息的长度,后跟数据本身;或者只发送数据,后跟结束序列和校验和,这样您就知道没有丢失。这些设计有优点和缺点,这是itsel中的一个大主题f

您还需要
UdpCodec
来包含数据:从
SocketAddr
到当前正在进行的部分重新组装的消息的映射。在
decode
中,如果给您消息的开头,请将其复制到映射中并返回
Ok
。如果给您消息的中间部分,并且您已经有了开头对于映射中的消息(对于该
SocketAddr
),将缓冲区附加到现有缓冲区并返回
Ok
。当到达消息末尾时,返回整个内容并清空缓冲区。
UdpCodec
上的方法采用
和mut self
,以启用此用例。(NB理论上,您还应该处理无序到达的数据包,但这在现实世界中非常罕见。)

encode
简单得多:只需添加相同的帧并将消息复制到缓冲区

让我在这里重申,在调用
framed()
之后,您不需要也不应该使用底层套接字。
UdpFramed
既是源又是接收器,因此您也可以使用该对象发送回复。您甚至可以使用
split()
从it中分离出
接收器
实现,前提是这样可以使应用程序中的所有权更容易


总的来说,现在我已经了解了您所面临的问题,我建议您只使用几个TCP套接字而不是UDP。如果您想要一个面向连接的、可靠的协议,TCP已经存在并为您实现了这一点。很容易在UDP上花费大量时间创建一个“可靠”层,既慢又慢
fn main() -> () {
    let addr = format!("127.0.0.1:{port}", port = 8080);
    let addr = addr.parse::<SocketAddr>().expect(&format!("Couldn't create valid SocketAddress out of {}", addr));

    let mut core = Core::new().unwrap();
    let handle = core.handle();
    let socket = UdpSocket::bind(&addr, &handle).expect(&format!("Couldn't bind socket to address {}", addr));


    let udp_future = socket.framed(MyCodec {}).for_each(|(addr, data)| {
        socket.send_to(&data, &addr); // Just echo back the data
        Ok(())
    });

    core.run(udp_future).unwrap();
}

struct MyCodec;

impl UdpCodec for MyCodec {
    type In = (SocketAddr, Vec<u8>);
    type Out = (SocketAddr, Vec<u8>);

    fn decode(&mut self, src: &SocketAddr, buf: &[u8]) -> io::Result<Self::In> {
        Ok((*src, buf.to_vec()))
    }

    fn encode(&mut self, msg: Self::Out, buf: &mut Vec<u8>) -> SocketAddr {
        let (addr, mut data) = msg;
        buf.append(&mut data);
        addr
    }
}
pub fn framed<C: UdpCodec>(self, codec: C) -> UdpFramed<C>
let framed = {
    let socket = UdpSocket::bind(&addr, &handle).expect(&format!("Couldn't bind socket to address {}", addr));
    socket.framed(MyCodec {})
}

let udp_future = framed.for_each(|(addr, data)| {
    info!(self.logger, "Udp packet received from {}: length: {}", addr, data.len());
    framed.get_ref().send_to(&data, &addr); // Just echo back the data

    Ok(())
});