7 个版本
0.1.7 | 2023年4月11日 |
---|---|
0.1.6 | 2023年4月11日 |
0.1.5 | 2021年12月7日 |
0.1.3 | 2020年2月17日 |
0.1.1 | 2019年10月13日 |
#1185 in 网络编程
每月 48 次下载
35KB
566 行

Grubbnet
Grubbnet 是一个轻量级 TCP 客户端/服务器库,旨在编写网络应用程序和游戏。它是我通常在网络项目中编写所有 TCP 模板的组合。最初,它是一个用于 多人 RPG 的内部 crate。
Grubbnet 抽象出套接字代码,跟踪连接,并将所有内容以事件列表的形式返回给开发者。除了处理网络事件(如客户端连接和断开)外,处理传入的数据包就像获取传入数据包队列的迭代器一样简单。
头部和数据包
而不是处理原始字节,Grubbnet 基于开发者可以定义的数据包操作。您可以通过实现 PacketBody
trait 将一个结构体转换为数据包,然后由开发者来定义他们想要的序列化和反序列化。在运行时,创建一个数据包头部,将序列化的数据包体附加到该头部上,并将完整的数据包发送到线路上。
数据包头部为 3 个字节(2 个字节用于 16 位体大小,1 个字节用于 8 位数据包 ID)。未来,我希望允许开发者定义自己的头部以提供更多灵活性。头部允许 Grubbnet 识别它是否正在接收数据包,数据包类型是什么,以及它需要等待多少字节才能获得重建数据包所需的所有数据。在此之后,数据包 ID 和(仍为序列化状态)数据包体将通过传入数据包队列返回给开发者,他们可以自由地对其进行操作。
用法
将以下内容添加到您的 Cargo.toml
[dependencies]
grubbnet = "0.1"
托管一个基本服务器,该服务器发送一个简单的数据包
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct MessagePacket { pub msg: String }
impl PacketBody for MessagePacket {
fn box_clone(&self) -> Box<dyn PacketBody> {
// This is used internally. Hopefully it can be removed some day.
Box::new((*self).clone())
}
fn serialize(&self) -> Vec<u8> {
// Define your own serialization here.
// I like to use serde & bincode.
bincode::config()
.big_endian()
.serialize::<Self>(&self)
.unwrap()
}
fn deserialize(_data: &[u8]) -> Self {
panic!("Attempted to deserialize a server-only packet!");
}
fn id(&self) -> u8 {
0x00
}
}
fn main() -> Result<()> {
let mut server = Server::host("127.0.0.1", 7667, 32)?;
loop {
// Run the network tick and process any events it generates
for event in server.tick().iter() {
match event {
ServerEvent::ClientConnected(token, addr) => {
// Send a message packet when a client connects
let pckt = MessagePacket { msg: "Hello, world!".to_owned() };
server.send(PacketRecipient::Single(*token), pckt);
}
ServerEvent::ClientDisconnected(token) => {}
ServerEvent::ConnectionRejected(addr) => {}
ServerEvent::ReceivedPacket(token, byte_count) => {}
ServerEvent::SentPacket(token, byte_count) => {}
_ => eprintln!("Unhandled ServerEvent!"),
}
}
// Process incoming packets
for (token, packet) in server.drain_incoming_packets().iter() {
match packet.header.id {
0x00 => {
// Deserialize and handle, however you like
}
_ => eprintln!("Unhandled packet! id: {}", packet.header.id)
}
}
}
}
示例
我编写了一个示例(分为两部分):一个简单的 ping 服务器和客户端。
cargo run --example simple_server
cargo run --example simple_client
服务器将运行,等待客户端连接并发送 PingPacket
,以 PongPacket
进行响应。客户端将连接并在间隔时间内发送 PingPacket
。当发送了 5 个 ping 后,服务器将踢掉客户端。
简单服务器(simple_server)和简单客户端(simple_client)都定义了 PingPacket
和 PongPacket
。在实际使用该库时,如果可能的话,应将数据包定义存储在公共库中。不用说,这两个数据包不需要是不同的类型。然而,我想展示客户端独有和服务器独有数据包的示例模式。
可选功能 - 加密
此库中有一个可选功能,即 crypto
。启用它将为您提供访问 grubbnet::crypto
模块的权限,该模块是对一些 openssl
和 bcrypt
功能的微小包装,用于使用私钥解密字节和散列/验证字符串。这对于编写包含敏感数据的数据包很有用。我需要写更多关于如何使用它的内容,但现在只需知道,如果您启用此功能,您需要在您的机器上安装 openssl
开发库。
// Decrypt some bytes
let (decrypted_bytes, decrypted_len) = grubbnet::crypto::decrypt(rsa, &encrypted_bytes)
.expect("Failed to decrypt password!");
// Convert those bytes into a UTF-8 plaintext password
let plaintext_password = str::from_utf8(&decrypted_bytes[0..decrypted_len])
.expect("Password was invalid UTF-8!");
// Verify plaintext password against some hashed password
if grubbnet::crypto::verify(plaintext_password, hashed_password)? {
println!("Authorized!");
}
许可证
Grubbnet 在 MIT 许可证的条款下分发。有关详细信息,请参阅 LICENSE。
依赖项
~1–12MB
~106K SLoC