#netcode #udp #udp-packet #networking #gamedev #udp-socket

pickleback

不可靠数据报交换的可靠性层

1个不稳定版本

0.1.0 2023年12月5日

#10#netcode

MIT/Apache

175KB
3.5K SLoC

Pickleback

一种在不可靠的数据报流上复用和合并消息的方法,用于游戏网络代码。

预计这将连接到UDP套接字;此crate没有内置网络。

当前状态

开发中。如果你想要讨论,请在bevy的Discord上联系我,我是RJ。

存在一个用于在PiclebackServer和PicklebackClient之间建立会话的非加密握手协议。在握手之后,大部分有用的操作发生在交换Message数据包时。请参阅测试工具包以获取示例用法。

设计目标

支持大多数更新以不可靠方式发送的多玩家游戏,偶尔需要可靠有序消息,例如游戏内聊天和加入状态同步。

预计双向交换数据包。在实践中,这意味着即使没有要传输的明确消息,每秒也会在每个方向发送至少10个数据包。

特性

  • 将多个小消息合并成数据包
  • 透明地分割和重组超过一个数据包大小的消息
  • 多个虚拟通道用于发送/接收消息
  • 可选的可靠通道,具有可配置的重传行为
  • 发送消息时,您将获得一个句柄,可用于检查确认(即使在不可靠通道上)
  • 内部缓冲池用于消息和数据包,以最小化分配
  • 无异步:设计用于集成到现有的游戏循环中。每次tick时调用它。
  • 单元测试和与坏链路模拟器的集成/浸泡测试,该模拟器丢弃、复制和重新排序数据包。
  • 计算rtt(需要在测试工具包外部验证)
  • 计算数据包丢失估计(需要在测试工具包外部验证)
  • 在有序通道上强制执行顺序(目前仅支持可靠性)
  • 带宽跟踪和预算
  • 允许配置通道和通道设置(目前只有1个可靠的,1个不可靠的)
  • 在选择要发送的消息时优先考虑通道。低量可靠的优先吗?

待办事项

  • 将一些函数代理到ConnectedClient以整理服务器端API
  • 基准测试,包括有和没有池化缓冲区的情况。
  • 使用bevy和不可靠传输机制的示例。

示例

use pickleback::prelude::*;

let config = PicklebackConfig::default();
let time = 0.0;

let mut server = PicklebackServer::new(time, &config);
let mut client = PicklebackClient::new(time, &config);
// Address must be valid, but isn't used in this example:
client.connect("127.0.0.1:0");

// in lieu of sending over a network, deliver directly:
fn transmit_packets(server: &mut PicklebackServer, client: &mut  PicklebackClient) {
    // Server --> Client
    {
        server.update(0.1);
        let mut send_to_client = |address, packet: BufHandle| {client.receive(packet.as_slice(), address); };
        server.visit_packets_to_send(&mut send_to_client);
    }
    // Client --> Server
    {
        client.update(0.1);
        let mut send_to_server = |address, packet: BufHandle| {server.receive(packet.as_slice(), address); };
        client.visit_packets_to_send(&mut send_to_server);
    }
}

// need to have some back-and-forth here to finish handshaking
// this would usually happen each tick of your game event loop.

// client sends ConnectionRequest
transmit_packets(&mut server, &mut client);
// server responds with ConnectionChallenge
transmit_packets(&mut server, &mut client);
// client responds with ConnectionChallengeResponse
transmit_packets(&mut server, &mut client);
// server sends a keep-alive, denoting fully connected
transmit_packets(&mut server, &mut client);

assert_eq!(client.state(), &ClientState::Connected);

let channel: u8 = 0;

// this can return an error if throttled due to backpressure and unable to send.
// we unwrap here, since it will not fail at this point.
let msg_id = {
    let mut connected_client = server.connected_clients_mut().next().unwrap();
    connected_client.pickleback.send_message(channel, b"hello").unwrap()
};
// server sends a Message packet containing a single message.
// (when sending multiple messages, they are coalesced into as few packets as possible)
transmit_packets(&mut server, &mut client);

// client will have received a message:
let received = client.drain_received_messages(channel).collect::<Vec<_>>();
assert_eq!(received.len(), 1);
// normally you'd use the .payload() to get a Reader, rather than .payload_to_owned()
// which reads it into a Vec. But a vec here makes it easier to test.
let recv_payload = received[0].payload_to_owned();
assert_eq!(b"hello".to_vec(), recv_payload);

// if the client doesn't have a message payload to send, it will still send
// a packet here just to transmit acks
transmit_packets(&mut server, &mut client);

// now the client has sent packets to the server, the server will have received an ack
let mut connected_client = server.connected_clients_mut().next().unwrap();
// TODO: don't expose pickleback via connected_client, proxy a couple of fns and document..
assert_eq!(vec![msg_id], connected_client.pickleback.drain_message_acks(channel).collect::<Vec<_>>());

协议概述

数据包由数据包类型、序列号、确认头和有效载荷组成。

ack头部确认已收到远程端点发送的最后一个N个数据包。

消息数据包负载由一个或多个消息组成。消息可以是任何大小,大消息会被分割成1024字节的片段消息,并为您重新组装。

调用send_message后,您会得到一个MessageId。一旦您的消息所在的数据包被确认,您将收到对您的MessageId的确认。不可靠的通道也会收到确认(除非数据包丢失)。

发送大于1024字节的消息时,您会在所有片段都成功交付并且消息被成功重新组装后收到确认。

可靠的通道会在可配置的时间内重传尚未确认的消息。

消息会被重传,数据包则不会。也就是说,尚未确认的可靠消息将包含在新的未来数据包中,直到它们被确认。

消息大小

任意限制为1024个1024B的片段,因此最大消息大小为1MB。

记住,随着片段数量的增加,数据包丢失的影响会被放大。

在1%的数据包丢失率下,10个片段的丢失概率为1 - (0.99^10) = 9.6%。

来源

依赖项

~4–5.5MB
~100K SLoC