9个版本

0.2.0 2022年4月18日
0.1.7 2022年4月18日
0.1.3 2022年3月29日

#2115 in 编码

Download history 32/week @ 2024-03-31

81 每月下载量

MIT 许可证

36KB
615

WSBPS-rust

我的Rust WebSocket二进制报文实现。这个库只实现了报文的序列化和反序列化,让您可以使用您喜欢的任何WebSocket系统。

指南

指南用于包宏中,用于定义每个对象应实现哪种类型的特质。这可以减少在不需要时实现的特质的数量。

指南表示为箭头。以下是对每个箭头的含义

箭头在代码中表示为 <-, ->, 和 <-> 美观的箭头仅在文档中使用

← 左箭头

<- 左箭头用于只读数据,这将只为此项实现可读特质。

→ 右箭头

-> 右箭头用于只写数据,这将只为此项实现可写特质。

↔ 双头箭头

<-> 双头箭头用于可读可写的数据,这实现了可读和可写特质。

数据类型

以下是可以通过此报文系统传输的数据类型表,以及它们在其它语言中的对应类型和一些自定义数据类型。

标准数字类型

Rust类型 范围 Javascript (wsbps-js) 长度(字节)
i8 -128到127 number (i8) 1
i16 -32768到32767 number (i16) 2
i32 -2147483648到2147483647 number (i32) 4
u8 0到255 number (u8) 1
u16 0到65535 number (u16) 2
u32 0到4294967295 number (u32) 4
f32 -3.4e+38到3.4e+38 number (f32) 4
f64 -1.7e+308到+1.7e+308 number (f64) 4

表中列出的所有数字类型都使用大端序进行编码

可变长度数字

VarInt

VarInts是一种可以大小从0到4294967295的范围的数字,可以作为从1字节到4字节长度的二进制数据发送

VarInts / VarLongs 以每 7 位一次进行序列化,从最低有效位(lsb)开始,每个输出字节的最高有效位(msb)表示是否存在续传字节(msb = 1)。

示例

VarInt 二进制 字节格式
1 00000001 1
127 01111111 127
128 10000000 00000001 128, 1
255 11111111 00000001 255, 1
300 10101100 00000010 172, 2
16384 10000000 10000000 00000001 128, 128, 1
2097152 10000000 10000000 10000000 00000001 128, 128, 128, 1

如您所见,这种格式在存储可变长度的数据方面效率更高,但是 VarInt 的最大长度与 u32(无符号 32 位整数)相同。

VarLong

VarInt 数据类型只能向上移动 5 个偏移量,这限制了它只能处理 u32 数字。另一方面,VarLong 可以向上移动多达 10 个偏移量,这意味着它可以编码和处理从 0 到 18446744073709551615(u64)的所有数字。

VarLongs 的编码方式与 VarInts 相同,只是当读取时允许更多的移位,这些移位被分开以减少 VarInts 的内存分配,因此除非必要,否则不需要将它们分配为 u64(VarLong)。

布尔值

布尔值编码为一个字节,1 表示真值,0 表示假值。

字符串

字符串使用 VarInt 编码字符串长度,后面跟着 UTF-8 编码的字节序列,长度等于 VarInt 长度减 1。

Length VarInt
Contents [u8; Length]

数组

数组数据类型使用向量,这些向量以与字符串相同的方式进行编码,首先是数组长度的 VarInt,然后是该数组所有相应值的编码序列,紧跟在 VarInt 之后。

Length VarInt
For Length {
    Item Any
}

您可以使用 Vec<Type> 来表示这些类型,这是此实现的常见方式,它被表示为 Vec<u8>

包组

要创建包,您使用包宏。在宏内部,您必须指定包 "组"。这些组用于处理客户端和服务器包 ID 之间的差异。定义组的语法如下

use wsbps::*;
packets! {
    GroupName (Direction) {
        //... Packets
    }
}

在这个例子中,您将用包组的名称替换 "GroupName",并用表示此包组方向的箭头替换 "Direction"。

此宏将生成一个具有提供的组名的枚举,如果实现了读取方向,则可以用来读取包。

然后,您可以按照以下结构定义包。应在组块内部使用此结构。

Name (ID) {
    example: u8
    // Normal struct field:type
}

在这个例子中,您应该用包的名称替换 "Name"。您提供的名称也是生成的结构体的名称。"ID" 应替换为该包的唯一标识符。ID 使用 VarInt 编码,因此它们可以从 0x00000000 - 0xffffffff(0 - 4294967295)。

以下是将包组和包实现结合在一起的示例。

use wsbps::*;

packets! {
    BiPackets (<->) {
        APacket (0x01) {
            user: u8
        }
    }
    
    ServerPackets (->) {
        BPacket (0x00) {
            name: u8
        }
    }
    
    ClientPackets (<-) {
        CPacket (0x00) {
            test: u8,
            test2: u8
        }
    }
}

结构体 & 枚举

如果您想在包中使用自定义的结构体或枚举,有两种选择。

选项 1

packet_data 宏

您可以使用 packet_data 宏轻松创建用于包中的枚举和结构体。此宏将自动为提供的枚举/结构体生成所需的读取和写入特性。

use wsbps::*;

packet_data! {
    enum Test (<->) (VarInt) {
        X: 1,
        B: 999
    }
    
    struct TestStruct (->) {
        Name: String
    }
}

第一组括号包含枚举/结构体类型的 "Direction",它告诉它需要实现哪些特性。枚举中的第二组括号包含枚举的数据类型,在这种情况下,使用 VarInt 数据类型。任何整数数据类型都是可接受的。

选项 2

如果您的数据需要自定义编码,或者过于复杂,无法在结构体或枚举中描述,您可以手动实现io模块中的Readable和Writable特质。

impl Writable for SomeType {
    fn write<B: Write>(&mut self, o: &mut B) -> Result<()> {
       // Your writing logic
        Ok(())
    }
}

impl Readable for SomeType {
    fn read<B: Read>(i: &mut B) -> Result<Self> where Self: Sized {
        // Your reading logic
    }
}

依赖项

~0.4–0.9MB
~20K SLoC