#tcp-client #client-server #tcp-server #packet-header #applications #multiplayer #networked

grubbnet

轻量级 TCP 客户端/服务器库,用于编写网络应用程序和游戏

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 次下载

MIT 许可证

35KB
566

Grubbnet icon

Grubbnet

crates.io License: MIT Documentation

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)都定义了 PingPacketPongPacket。在实际使用该库时,如果可能的话,应将数据包定义存储在公共库中。不用说,这两个数据包不需要是不同的类型。然而,我想展示客户端独有和服务器独有数据包的示例模式。

可选功能 - 加密

此库中有一个可选功能,即 crypto。启用它将为您提供访问 grubbnet::crypto 模块的权限,该模块是对一些 opensslbcrypt 功能的微小包装,用于使用私钥解密字节和散列/验证字符串。这对于编写包含敏感数据的数据包很有用。我需要写更多关于如何使用它的内容,但现在只需知道,如果您启用此功能,您需要在您的机器上安装 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