Rust 如何使用东京';s UdpSocket在1服务器:N客户端设置中处理消息?
我想做什么: 。。。编写一个(1)服务器/(N)客户端(网络游戏-)体系结构,使用UDP套接字作为通信的基础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(东
- 消息以
的形式发送,通过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(())
});