【Laya】Byte 二进制数据处理

Laya.Byte 二进制数据处理

简介

Laya.Byte 类提供了用于二进制数据的读取、写入和处理的方法。该类适合需要在字节级别访问数据的场景,如网络通信、文件解析、协议实现等。

适用场景

  • WebSocket/Socket 网络通信的数据封包和解包
  • 二进制文件格式解析(如图片、音频、自定义格式)
  • 游戏协议的序列化与反序列化
  • 与后端二进制接口的数据交互

工作原理

复制代码
写入数据 → 移动 pos 指针 → 重置 pos → 读取数据 → 循环

核心优势

优势 说明
高效的二进制操作 直接操作字节,性能优异
支持多种数据类型 整数、浮点数、字符串、数组
灵活的字节序 支持大端序和小端序转换
便于网络通信 与 Socket 无缝配合

目录


API 参考

静态属性

属性 类型 描述
BIG_ENDIAN string "bigEndian" 大端字节序(网络字节序)
LITTLE_ENDIAN string "littleEndian" 小端字节序

静态方法

方法 返回值 描述
getSystemEndian() string 获取当前系统的字节序

实例属性

属性 类型 描述
buffer ArrayBuffer 获取有效数据的 ArrayBuffer
rawBuffer ArrayBuffer 获取 ArrayBuffer 引用
endian string 实例的字节序,可读写
length number Byte 对象的长度(字节)
pos number 读写指针的当前位置
bytesAvailable number 可读取的剩余字节数

整数读写

方法 参数 返回值 描述
writeByte(value) number void 写入带符号字节(-128~127)
readByte() - number 读取带符号字节
writeUint8(value) number void 写入无符号 8 位整数
readUint8() - number 读取无符号 8 位整数
writeInt16(value) number void 写入带符号 16 位整数
readInt16() - number 读取带符号 16 位整数
writeUint16(value) number void 写入无符号 16 位整数
readUint16() - number 读取无符号 16 位整数
writeInt32(value) number void 写入带符号 32 位整数
readInt32() - number 读取带符号 32 位整数
writeUint32(value) number void 写入无符号 32 位整数
readUint32() - number 读取无符号 32 位整数

浮点数读写

方法 参数 返回值 描述
writeFloat32(value) number void 写入 IEEE 754 单精度浮点数
readFloat32() - number 读取 IEEE 754 单精度浮点数
writeFloat64(value) number void 写入 IEEE 754 双精度浮点数
readFloat64() - number 读取 IEEE 754 双精度浮点数

字符串读写

方法 参数 返回值 描述
writeUTFBytes(value) string void 写入 UTF-8 字符串,无长度前缀
readUTFBytes(len?) number string 读取 UTF-8 字节序列
writeUTFString(value) string void 先写入 16 位长度,再写入字符串
readUTFString() - string 读取 UTF-8 字符串
writeUTFString32(value) string void 先写入 32 位长度,再写入字符串
readUTFString32() - string 读取 UTF-8 字符串(32位长度)
readString() - string 先读 Uint16 长度,再读字符串

数组读写

方法 参数 返回值 描述
readUint8Array(start, len) number, number Uint8Array 读取 Uint8Array
readInt8Array(start, len) number, number Int8Array 读取 Int8Array
readInt16Array(start, len) number, number Int16Array 读取 Int16Array
readFloat32Array(start, len) number, number Float32Array 读取 Float32Array
writeArrayBuffer(buf, off, len) ArrayBuffer, ... void 写入 ArrayBuffer 数据
readArrayBuffer(length) number ArrayBuffer 读取 ArrayBuffer

工具方法

方法 描述
clear() 清除内容,重置 length 和 pos 为 0

基础用法

1. 基本读写

最简单的数据写入和读取:

typescript 复制代码
// 创建 Byte 对象
let byte = new Laya.Byte();

// 写入数据
byte.writeInt32(123456);
byte.writeFloat32(3.14);
byte.writeUTFString("Hello LayaAir");

// 重置位置以便读取
byte.pos = 0;

// 读取数据
let intVal = byte.readInt32();      // 123456
let floatVal = byte.readFloat32();  // 3.14
let strVal = byte.readUTFString();  // "Hello LayaAir"

console.log(intVal, floatVal, strVal);

2. 字节序处理

跨平台通信时需要注意字节序:

typescript 复制代码
let byte = new Laya.Byte();

// 获取系统字节序
let sysEndian = Laya.Byte.getSystemEndian();
console.log("系统字节序:", sysEndian);  // "littleEndian" 或 "bigEndian"

// 设置字节序
byte.endian = Laya.Byte.BIG_ENDIAN;

byte.writeUint16(0x1234);
byte.pos = 0;
let value = byte.readUint16();
console.log("读取值:", value.toString(16));  // "1234"

3. 从 ArrayBuffer 创建

typescript 复制代码
// 从现有 ArrayBuffer 创建
let arrayBuffer = new ArrayBuffer(100);
let byte = new Laya.Byte(arrayBuffer);

// 或指定初始大小
let byte2 = new Laya.Byte(1024);

实用示例

示例1: 自定义协议封包

typescript 复制代码
export class PacketCodec {
    // 协议头定义
    private static readonly HEADER = "PB";
    private static readonly VERSION = 1;

    // 封包:消息类型 + 数据
    public static encode(msgType: number, data: string): Laya.Byte {
        let byte = new Laya.Byte();

        // 写入协议头
        byte.writeUTFBytes(this.HEADER);
        byte.writeUint8(this.VERSION);

        // 写入消息类型
        byte.writeUint16(msgType);

        // 写入数据内容
        byte.writeUTFString(data);

        return byte;
    }

    // 解包:解析消息类型和数据
    public static decode(byte: Laya.Byte): { msgType: number, data: string } | null {
        byte.pos = 0;

        // 验证协议头
        let header = byte.readUTFBytes(2);
        if (header !== this.HEADER) {
            console.error("无效的协议头");
            return null;
        }

        // 读取版本
        let version = byte.readUint8();
        if (version !== this.VERSION) {
            console.error("不支持的协议版本:", version);
            return null;
        }

        // 读取消息类型和数据
        let msgType = byte.readUint16();
        let data = byte.readUTFString();

        return { msgType, data };
    }
}

// 使用示例
let packet = PacketCodec.encode(1001, "Hello Server");
let decoded = PacketCodec.decode(packet);
console.log(decoded);  // { msgType: 1001, data: "Hello Server" }

示例2: WebSocket 通信

typescript 复制代码
@regClass()
export class NetworkManager extends Laya.Script {
    private socket: Laya.Socket;
    private output: Laya.Byte;

    onStart(): void {
        this.socket = new Laya.Socket();
        this.socket.connectByUrl("wss://game-server.com:443");
        this.output = this.socket.output;

        // 监听事件
        this.socket.on(Laya.Event.OPEN, this, this.onOpen);
        this.socket.on(Laya.Event.MESSAGE, this, this.onMessage);
        this.socket.on(Laya.Event.CLOSE, this, this.onClose);
        this.socket.on(Laya.Event.ERROR, this, this.onError);
    }

    private onOpen(): void {
        console.log("连接成功");

        // 发送登录请求
        this.sendLogin("player001", "token123");
    }

    // 发送登录请求
    public sendLogin(username: string, token: string): void {
        this.output.writeUint16(1001);  // 消息类型:登录
        this.output.writeUTFString(username);
        this.output.writeUTFString(token);
        this.socket.flush();
    }

    // 发送位置同步
    public sendPosition(x: number, y: number): void {
        this.output.writeUint16(2001);  // 消息类型:位置同步
        this.output.writeFloat32(x);
        this.output.writeFloat32(y);
        this.socket.flush();
    }

    private onMessage(msg: any): void {
        if (typeof msg === "string") {
            console.log("收到文本消息:", msg);
        } else if (msg instanceof ArrayBuffer) {
            // 解析二进制消息
            let byte = new Laya.Byte(msg);
            this.handleBinaryMessage(byte);
        }
        this.socket.input.clear();
    }

    private handleBinaryMessage(byte: Laya.Byte): void {
        let msgType = byte.readUint16();

        switch (msgType) {
            case 1001:
                this.handleLoginResponse(byte);
                break;
            case 3001:
                this.handlePlayerState(byte);
                break;
            default:
                console.warn("未知消息类型:", msgType);
        }
    }

    private handleLoginResponse(byte: Laya.Byte): void {
        let success = byte.readUint8() === 1;
        let playerId = byte.readUTFString();
        console.log("登录响应:", success, playerId);
    }

    private handlePlayerState(byte: Laya.Byte): void {
        let playerId = byte.readUTFString();
        let x = byte.readFloat32();
        let y = byte.readFloat32();
        console.log("玩家状态:", playerId, x, y);
    }

    private onClose(): void {
        console.log("连接关闭");
    }

    private onError(): void {
        console.log("连接错误");
    }

    onDestroy(): void {
        this.socket.close();
        this.socket.offAll();
    }
}

示例3: 二进制文件解析

typescript 复制代码
export class BinaryFileParser {
    // 解析自定义二进制文件
    public static parse(arrayBuffer: ArrayBuffer): any {
        let byte = new Laya.Byte(arrayBuffer);
        byte.endian = Laya.Byte.LITTLE_ENDIAN;

        // 读取文件头
        let magic = byte.readUTFBytes(4);
        if (magic !== "DATA") {
            throw new Error("无效的文件格式");
        }

        let version = byte.readUint16();
        let recordCount = byte.readUint32();

        let records = [];

        // 读取记录
        for (let i = 0; i < recordCount; i++) {
            let record = this.readRecord(byte);
            records.push(record);
        }

        return { version, records };
    }

    private static readRecord(byte: Laya.Byte): any {
        let id = byte.readInt32();
        let type = byte.readUint8();
        let x = byte.readFloat32();
        let y = byte.readFloat32();
        let name = byte.readUTFString();

        return { id, type, x, y, name };
    }

    // 写入二进制文件
    public static write(records: any[]): Laya.Byte {
        let byte = new Laya.Byte();
        byte.endian = Laya.Byte.LITTLE_ENDIAN;

        // 写入文件头
        byte.writeUTFBytes("DATA");
        byte.writeUint16(1);  // 版本
        byte.writeUint32(records.length);

        // 写入记录
        for (let record of records) {
            byte.writeInt32(record.id);
            byte.writeUint8(record.type);
            byte.writeFloat32(record.x);
            byte.writeFloat32(record.y);
            byte.writeUTFString(record.name);
        }

        return byte;
    }
}

// 使用示例
let data = [
    { id: 1, type: 0, x: 100, y: 200, name: "对象1" },
    { id: 2, type: 1, x: 300, y: 400, name: "对象2" }
];

let fileData = BinaryFileParser.write(data);
let parsed = BinaryFileParser.parse(fileData.buffer);
console.log(parsed);

示例4: 位置数据回写

typescript 复制代码
// 先写入数据,再回写长度
let byte = new Laya.Byte();

// 写入标识
byte.writeUTFBytes("HEAD");

// 预留长度位置(4字节)
byte.writeInt32(0);
let lengthPos = byte.pos - 4;

// 写入数据内容
byte.writeInt32(100);
byte.writeFloat32(3.14);
byte.writeUTFString("Hello");

// 回写数据长度
let currentPos = byte.pos;
let dataLength = currentPos - lengthPos - 4;
byte.pos = lengthPos;
byte.writeInt32(dataLength);
byte.pos = currentPos;

// 读取验证
byte.pos = 0;
console.log(byte.readUTFBytes(4));  // "HEAD"
console.log(byte.readInt32());      // 数据长度

示例5: 批量数据处理

typescript 复制代码
// 批量写入顶点数据
let byte = new Laya.Byte();

let vertices = [
    { x: 0, y: 0, z: 0 },
    { x: 100, y: 0, z: 0 },
    { x: 0, y: 100, z: 0 },
    { x: 100, y: 100, z: 0 }
];

// 写入顶点数量
byte.writeUint32(vertices.length);

// 写入顶点数据
for (let v of vertices) {
    byte.writeFloat32(v.x);
    byte.writeFloat32(v.y);
    byte.writeFloat32(v.z);
}

// 批量读取
byte.pos = 0;
let count = byte.readUint32();
let loadedVertices = [];

for (let i = 0; i < count; i++) {
    loadedVertices.push({
        x: byte.readFloat32(),
        y: byte.readFloat32(),
        z: byte.readFloat32()
    });
}

console.log(loadedVertices);

高级技巧

1. 使用常量定义协议字段

typescript 复制代码
export class Protocol {
    // 消息类型
    public static readonly MSG_LOGIN = 1001;
    public static readonly MSG_MOVE = 1002;
    public static readonly MSG_ATTACK = 1003;

    // 数据偏移量
    public static readonly OFFSET_MSG_TYPE = 0;
    public static readonly OFFSET_MSG_LEN = 2;
    public static readonly OFFSET_MSG_DATA = 4;
}

2. 创建 Byte 复用池

typescript 复制代码
export class BytePool {
    private static pool: Laya.Byte[] = [];

    public static get(): Laya.Byte {
        if (this.pool.length > 0) {
            let byte = this.pool.pop();
            byte.clear();
            return byte;
        }
        return new Laya.Byte();
    }

    public static recover(byte: Laya.Byte): void {
        if (this.pool.length < 50) {
            this.pool.push(byte);
        }
    }
}

3. 检查可读数据

typescript 复制代码
function safeRead(byte: Laya.Byte): void {
    // 检查是否有足够数据可读
    if (byte.bytesAvailable < 12) {
        console.warn("数据不足");
        return;
    }

    // 安全读取
    let a = byte.readInt32();
    let b = byte.readFloat32();
    let c = byte.readUint32();
}

4. 保存和恢复读取位置

typescript 复制代码
function peekData(byte: Laya.Byte): number {
    // 保存当前位置
    let savedPos = byte.pos;

    // 读取数据
    let value = byte.readInt32();

    // 恢复位置
    byte.pos = savedPos;

    return value;
}

最佳实践

1. 读写配对原则

typescript 复制代码
// ✅ 正确:读写顺序一致
byte.writeInt32(100);
byte.writeFloat32(3.14);
byte.writeUTFString("Hello");

byte.pos = 0;
let a = byte.readInt32();
let b = byte.readFloat32();
let c = byte.readUTFString();

// ❌ 错误:读写顺序不一致会导致数据错乱

2. 网络通信统一字节序

typescript 复制代码
// 网络通信建议统一使用大端序
let byte = new Laya.Byte();
byte.endian = Laya.Byte.BIG_ENDIAN;  // 网络字节序

3. 使用完毕后清理

typescript 复制代码
// 网络消息处理完成后清理
let byte = new Laya.Byte(msg);
// ... 处理数据 ...
byte.clear();  // 释放内存

4. 字符串方法选择

方法 适用场景 特点
writeUTFString / readUTFString 网络协议 带 16 位长度前缀
writeUTFString32 / readUTFString32 大数据传输 带 32 位长度前缀
writeUTFBytes / readUTFBytes 固定长度或已知长度 无长度前缀,需指定长度

5. 错误处理

typescript 复制代码
export class SafeByteReader {
    public static readInt32Safe(byte: Laya.Byte): number | null {
        if (byte.bytesAvailable < 4) {
            console.error("数据不足,无法读取 Int32");
            return null;
        }
        return byte.readInt32();
    }

    public static readStringSafe(byte: Laya.Byte): string | null {
        try {
            return byte.readUTFString();
        } catch (e) {
            console.error("读取字符串失败:", e);
            return null;
        }
    }
}

注意事项

  1. 必须使用 Laya.Byte 前缀 :访问该类时需加上 Laya. 命名空间
  2. 读写顺序一致:写入和读取的顺序必须对应
  3. 注意 pos 指针:读写操作会自动移动 pos,读取前记得重置
  4. 字节序问题:跨平台通信需确保字节序一致
  5. 内存管理 :处理完成后调用 clear() 释放内存
  6. 避免废弃方法 :使用 read* 方法替代 get* 方法

相关文档

相关推荐
负二代0.03 小时前
Linux下的网络管理
linux·网络
欧洵.3 小时前
深入理解TCP/IP协议栈:数据链路层核心知识点解析
网络
雨声不在4 小时前
udp穿透的方法V2
网络·网络协议·udp
孟无岐4 小时前
【Laya】ClassUtils 类反射工具
typescript·游戏引擎·游戏程序·laya
嗨 ! 海洋4 小时前
K8S创建pod,CNI插件的网络配置过程
网络·kubernetes·php
尼古拉斯·纯情暖男·天真·阿玮4 小时前
实验十一 动态主机配置(DHCP)实验
网络·智能路由器
michael_ouyang5 小时前
WebSocket心跳方案选型与最佳实践
网络·websocket·网络协议
23124_805 小时前
HTTP头注入
网络·网络协议·http
June bug6 小时前
【配环境】unity项目开发环境
unity·游戏引擎