8个版本 (4个重大变更)
0.5.0 | 2023年9月6日 |
---|---|
0.4.1 | 2023年7月30日 |
0.3.2 | 2023年3月2日 |
0.3.0 | 2023年2月19日 |
0.1.0 | 2022年12月8日 |
#1376 in 网络编程
每月46次下载
用于 2 crates
200KB
2.5K SLoC
概述
"这个库很糟糕!" ... "除非你喜欢榴莲"
durian
是一个基于QUIC协议的客户端-服务器网络库,由 QUIC 协议的 quinn 在Rust中实现。
它提供了一层薄薄的抽象层,用于连接管理、字节管理、帧结构等低级细节,以便更容易编写网络代码,并允许用户专注于消息内容。序列化和反序列化已集成到API中,因此您可以使用结构体轻松发送和接收精确的包。
durian
是一个通用库,但主要是为了让我在游戏开发中尝试而创建的。它已与 Bevy 游戏引擎进行了测试并正常工作。
完整文档可在 https://docs.rs/durian/latest/durian/ 找到
Crates.io: https://crates.io/crates/durian/
免责声明
这个库处于非常早期(但非常活跃!)的开发阶段,这意味着它的许多部分都会快速变化。
在其当前状态下,它可以用于创建快速的多玩家演示。我自己使用它来学习 游戏开发。
但是,这不是生产就绪版本,并且缺少许多功能才能使其达到生产就绪状态(请参阅下面的 功能 列表)。
但是,如果您正在尝试构建某些内容,并希望避免许多低级网络代码细节的头痛,并且不需要“生产”功能,如多玩家游戏演示、局域网沙盒应用程序等,那么您可以自由尝试它!
durian
的目标是最简化网络代码的设置(请参见下面的示例)。
功能
- 同时进行基本的客户端/服务器连接管理和操作
- 适用于不同调用上下文的异步和同步API
- 无阻塞头部的多路复用(QUIC特性)
- 为每种数据包类型分配专用流和线程多路复用
- 可靠数据包:保证所有消息的投递
- 有序数据包:每个流上的数据包按发送顺序接收
- 自动为您进行数据包分片和重组
- 宏简化数据包创建和注册
- 同时发送和接收数据包
- 各种客户端/服务器配置
- keep-alive-intervals
- idle-timeout
- ALPN协议
尚未完成
- 客户端-服务器之间的证书认证
- 更复杂的连接配置,例如
- 可插拔的加密
- 握手协议
- 连接/流重新建立
- 在多个PacketManager(对于连接到多个服务器的客户端或具有不同数据包协议的客户端)之间重用端点
- 更好的错误处理/消息传递
- 不可靠数据包
- 无序数据包
- 可能还有更多
Cargo.toml依赖
通过cargo add durian
或手动将durian
添加到您的Cargo.toml中
[dependencies]
durian = "0.3"
数据包/数据包构建器
创建一个用于与durian
一起使用的Packet
需要两个步骤
-
durian
允许将Packets
结构化为简单的结构体。这些结构体必须实现Packet
特质,该特质包含一个用于将Packet
序列化为发送到客户端和服务器之间的字节的单函数Packet::as_bytes()
。 -
还需要一个实现
PacketBuilder
的结构体,该结构体用于将字节反序列化回您的Packet
结构体,通过PacketBuilder::read()
函数。
为了方便起见,durian
捆绑了durian_macros
,其中包含一些宏,这些宏有助于自动生成Packet
和PacketBuilder
的结构体的Impl块。唯一的要求是该结构体必须是可序列化的,这意味着所有嵌套字段也必须是可序列化的。
#[bincode_packet]
将使用bincode对您的Packet进行序列化和反序列化,并自动应用必要的 derive 宏。
use durian::bincode_packet;
// Automatically implements Packet, and generates a PositionPacketBuilder that implements
// PacketBuilder. You can also add other macros such as derive macros so long as they don't
// conflict with what #[bincode_packet] adds (See bincode_packet documentation).
#[bincode_packet]
#[derive(Debug)]
struct Position {
x: i32,
y: i32
}
// Works for Unit (empty) structs as well
#[bincode_packet]
struct Ack;
您还可以手动使用 derive 宏(BinPacket
和UnitPacket
)
use durian::serde::{Deserialize, Serialize};
use durian::{BinPacket, UnitPacket};
#[derive(Serialize, Deserialize, BinPacket)]
#[serde(crate = "durian::serde")]
struct Position { x: i32, y: i32 }
#[derive(UnitPacket)]
struct Ack;
数据包管理器
PacketManager
是您将用于在客户端/服务器之间建立连接,并发送/接收Packets
。
在每个客户端创建一个PacketManager
以连接到单个服务器,并在服务器上创建一个以连接到多个客户端。它包含同步和异步API,因此您可以从同步上下文或异步运行时(注意:同步路径将为每个PacketManager
实例创建一个单独的隔离的异步运行时上下文。)调用函数。
使用PacketManager
有4个基本步骤,这些步骤将在客户端和服务器端执行
-
通过
new()
或,如果从异步上下文调用,使用new_for_async()
创建一个PacketManager
。 -
使用
register_receive_packet()
和register_send_packet()
注册Packets
和PacketBuilders
,这些包将用于PacketManager
接收和发送。Packet
注册的顺序对于每个接收和发送通道都很重要 - 客户端和服务器必须以相同的顺序注册相同的包,对于相反的通道。- 换句话说,客户端必须以服务器注册相同顺序的相同顺序注册接收包,反之亦然,客户端必须以服务器注册相同顺序的相同顺序注册发送包。这有助于确保客户端和服务器在要发送/接收的包上保持同步,几乎就像确保它们处于相同的“版本”一样,并用于正确识别包。
- 为了方便起见,
durian_macros
附带了2个宏,用于简化发送和接收包的注册:register_receive!()
和register_send!()
-
使用
init_client()
(或在客户端侧使用异步版本async_init_client()
) 初始化连接,否则在服务器端使用init_server()
(或异步版本async_init_server)
)。 -
使用以下任意方法发送数据包:
broadcast()
,send()
或send_to()
。
将这些放在一起
use durian::{ClientConfig, PacketManager, bincode_packet, register_receive, register_send};
#[bincode_packet]
struct Position { x: i32, y: i32 }
#[bincode_packet]
struct ServerAck;
#[bincode_packet]
struct ClientAck;
#[bincode_packet]
struct InputMovement { direction: String }
fn packet_manager_example() {
// Create PacketManager
let mut manager = PacketManager::new();
// Register send and receive packets
// Using macros
let register_results = register_receive!(
manager,
(Position, PositionPacketBuilder),
(ServerAck, ServerAckPacketBuilder)
);
let send_results = register_send!(manager, ClientAck, InputMovement);
// Validate registrations succeeded
assert!(receive_results.iter().all(|r| r.is_ok()));
assert!(send_results.iter().all(|r| r.is_ok()));
// Or equivalently with manual registrations:
// manager.register_receive_packet::<Position>(PositionPacketBuilder).unwrap();
// manager.register_receive_packet::<ServerAck>(ServerAckPacketBuilder).unwrap();
// manager.register_send_packet::<ClientAck>().unwrap();
// manager.register_send_packet::<InputMovement>().unwrap();
// Initialize a client
let client_config = ClientConfig::new("127.0.0.1:5001", "127.0.0.1:5000", 2, 2);
manager.init_client(client_config).unwrap();
// Send and receive packets
manager.broadcast(InputMovement { direction: "North".to_string() }).unwrap();
manager.received_all::<Position, PositionPacketBuilder>(false).unwrap();
// The above PacketManager is for the client. Server side is similar except the packets
// are swapped between receive vs send channels:
// Create PacketManager
let mut server_manager = PacketManager::new();
// Register send and receive packets
let server_register_results = register_receive!(
server_manager,
(ClientAck, ClientAckPacketBuilder),
(InputMovement, InputMovementPacketBuilder)
);
let server_send_results = register_send!(server_manager, Position, ServerAck);
// Validate registrations succeeded
assert!(server_receive_results.iter().all(|r| r.is_ok()));
assert!(server_send_results.iter().all(|r| r.is_ok()));
// Initialize a client
let client_config = ClientConfig::new("127.0.0.1:5001", "127.0.0.1:5000", 2, 2);
server_manager.init_client(client_config).unwrap();
// Send and receive packets
server_manager.broadcast(Position { x: 1, y: 3 }).unwrap();
server_manager.received_all::<InputMovement, InputMovementPacketBuilder>(false).unwrap();
}
示例
对于初学者,创建客户端/服务器之间的数据包应该非常简单,上述示例应涵盖您所需的大部分内容。对于更复杂的场景,例如以自定义方式序列化/反序列化数据包,可以自行实现或通过在 multisnakegame 仓库 中进行额外配置来完成。
对于全面的示例,请参阅 example crate。
我还自己使用这个库进行简单的游戏开发。请参阅 multisnakegame 仓库。
调试
durian
使用日志记录和跟踪日志记录。启用调试记录以查看来自 durian
的更新日志,并启用跟踪记录以查看数据包字节传输。
依赖项
~16–27MB
~492K SLoC