【Laya】Socket 使用指南

Laya.Socket 使用指南

简介

Laya.Socket 是 LayaAir 引擎提供的 WebSocket 网络通信类,封装了 HTML5 WebSocket,实现服务器与客户端之间的全双工实时通信。

适用场景

  • 多人在线游戏(即时战斗、聊天)
  • 实时数据同步
  • 跨域通信
  • 长连接推送服务

工作原理

复制代码
建立连接 → 交换数据 → 心跳维持 → 连接关闭/重连

核心优势

优势 说明
全双工通信 服务器和客户端可主动互相发送数据
低延迟 基于 WebSocket 协议,实时性高
跨域支持 支持跨域通信,无需 JSONP
二进制支持 支持文本和二进制数据传输

目录


API 参考

构造函数

方法 参数 说明
constructor(host?: string, port?: number, protocols?: string[], isSecure?: boolean) 主机, 端口, 子协议, 是否安全连接 创建 Socket 对象,默认字节序为 BIG_ENDIAN

连接方法

方法 参数 返回值 说明
connect(host: string, port: number, isSecure?: boolean, protocols?: string[]) 主机, 端口, 安全协议, 子协议 void 连接到指定主机和端口
connectByUrl(url: string, protocols?: string[]) WebSocket URL, 子协议 void 通过 URL 连接服务器

数据发送

方法 参数 返回值 说明
`send(data: string ArrayBuffer)` 字符串或二进制数据 Promise<void>
flush() void 发送 output 缓冲区中的数据到服务器

连接控制

方法 参数 返回值 说明
close() void 关闭连接
cleanSocket() void 清理 Socket:关闭连接,移除监听,重置状态

属性

属性 类型 只读 说明
connected boolean 当前是否已连接
input Laya.Byte 从服务器接收的数据缓存
output Laya.Byte 需要发送到服务器的数据缓冲区
endian string 字节序(取值为 Laya.Byte.BIG_ENDIANLaya.Byte.LITTLE_ENDIAN
disableInput boolean 是否禁用输入缓存(字符串数据建议设为 true)

字节序常量 (来自 Laya.Byte):

常量 说明
Laya.Byte.LITTLE_ENDIAN "littleEndian" 小端字节序
Laya.Byte.BIG_ENDIAN "bigEndian" 大端字节序(默认)

事件

事件 触发时机 事件参数
Laya.Event.OPEN 连接成功建立 HTML DOM Event
Laya.Event.MESSAGE 接收到数据 数据内容(字符串或 ArrayBuffer)
Laya.Event.CLOSE 连接关闭 HTML DOM Event
Laya.Event.ERROR 发生错误 HTML DOM Event

基础用法

1. 通过 URL 连接(推荐)

最简洁的连接方式:

typescript 复制代码
class GameSocket {
    private socket: Laya.Socket;

    constructor() {
        this.socket = new Laya.Socket();

        // 监听事件
        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);

        // 连接服务器
        this.socket.connectByUrl("ws://localhost:8080");
    }

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

    private onMessage(data: any): void {
        if (typeof data === "string") {
            console.log("收到文本消息:", data);
        } else if (data instanceof ArrayBuffer) {
            console.log("收到二进制消息:", data);
        }
    }

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

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

2. 通过主机和端口连接

typescript 复制代码
// 普通 WebSocket 连接
let socket = new Laya.Socket();
socket.connect("localhost", 8080);

// 安全 WebSocket 连接 (wss)
socket.connect("localhost", 8080, true);

3. 发送字符串消息

typescript 复制代码
// 连接成功后发送
private onOpen(): void {
    // 直接发送字符串
    this.socket.send("Hello Server!");
}

4. 发送二进制数据

typescript 复制代码
private sendBinaryData(): void {
    // 方式1: 使用 send 直接发送 ArrayBuffer
    let buffer = new ArrayBuffer(4);
    let view = new DataView(buffer);
    view.setInt32(0, 12345);
    this.socket.send(buffer);

    // 方式2: 使用 output 缓冲区
    this.socket.output.writeByte(1);
    this.socket.output.writeInt32(12345);
    this.socket.output.writeUTFString("test");
    this.socket.flush();  // 必须调用 flush 发送
}

5. 接收并解析数据

typescript 复制代码
private onMessage(data: any): void {
    if (typeof data === "string") {
        // 处理字符串消息
        this.handleTextMessage(data);
    } else if (data instanceof ArrayBuffer) {
        // 处理二进制消息
        this.handleBinaryMessage(data);
    }
}

private handleBinaryMessage(buffer: ArrayBuffer): void {
    let byte = new Laya.Byte(buffer);
    byte.endian = Laya.Byte.BIG_ENDIAN;

    let msgType = byte.getByte();
    let msgId = byte.getInt32();
    let content = byte.getUTFString();

    console.log(`消息类型: ${msgType}, ID: ${msgId}, 内容: ${content}`);
}

实用示例

示例1: 基础 WebSocket 通信

typescript 复制代码
@regClass()
export class NetworkManager extends Laya.Script {
    private socket: Laya.Socket;
    private reconnectDelay: number = 3000;

    onAwake(): void {
        this.initSocket();
    }

    private initSocket(): void {
        this.socket = new Laya.Socket();
        this.socket.disableInput = true;  // 禁用输入缓存,使用直接接收

        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);

        this.connect();
    }

    private connect(): void {
        console.log("正在连接服务器...");
        this.socket.connectByUrl("ws://localhost:8080/ws");
    }

    private onOpen(): void {
        console.log("服务器连接成功");
        this.reconnectDelay = 3000;  // 重置重连延迟

        // 发送登录消息
        this.sendMessage({
            type: "login",
            userId: "player123",
            token: "xxx"
        });
    }

    private onMessage(data: any): void {
        if (typeof data === "string") {
            let msg = JSON.parse(data);
            this.handleMessage(msg);
        }
    }

    private onClose(): void {
        console.log("服务器连接关闭,尝试重连...");
        this.scheduleReconnect();
    }

    private onError(): void {
        console.log("连接错误,尝试重连...");
        this.scheduleReconnect();
    }

    private scheduleReconnect(): void {
        Laya.timer.clear(this, this.connect);  // 清除之前的重连定时器
        Laya.timer.once(this.reconnectDelay, this, this.connect);
        this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);  // 最大30秒
    }

    public sendMessage(msg: any): void {
        if (!this.socket.connected) {
            console.warn("未连接到服务器");
            return;
        }
        this.socket.send(JSON.stringify(msg));
    }

    private handleMessage(msg: any): void {
        switch (msg.type) {
            case "chat":
                this.onChatMessage(msg);
                break;
            case "move":
                this.onPlayerMove(msg);
                break;
            case "battle":
                this.onBattleUpdate(msg);
                break;
        }
    }

    private onChatMessage(msg: any): void {
        console.log(`${msg.player}: ${msg.content}`);
    }

    private onPlayerMove(msg: any): void {
        // 处理玩家移动
    }

    private onBattleUpdate(msg: any): void {
        // 处理战斗更新
    }

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

示例2: 二进制协议通信

typescript 复制代码
// 协议定义
enum MsgType {
    LOGIN = 1,
    MOVE = 2,
    ATTACK = 3,
    SYNC = 4
}

@regClass()
export class BinarySocket extends Laya.Script {
    private socket: Laya.Socket;

    onAwake(): void {
        this.socket = new Laya.Socket();
        this.socket.endian = Laya.Byte.LITTLE_ENDIAN;

        this.socket.on(Laya.Event.OPEN, this, this.onOpen);
        this.socket.on(Laya.Event.MESSAGE, this, this.onMessage);
        this.socket.connectByUrl("ws://localhost:8080");
    }

    private onOpen(): void {
        // 发送登录消息
        this.sendLogin("player123", "token");
    }

    // 发送登录消息
    public sendLogin(userId: string, token: string): void {
        this.socket.output.writeByte(MsgType.LOGIN);
        this.socket.output.writeUTFString(userId);
        this.socket.output.writeUTFString(token);
        this.socket.flush();
    }

    // 发送移动消息
    public sendMove(x: number, y: number): void {
        this.socket.output.clear();
        this.socket.output.writeByte(MsgType.MOVE);
        this.socket.output.writeFloat32(x);
        this.socket.output.writeFloat32(y);
        this.socket.flush();
    }

    // 发送攻击消息
    public sendAttack(targetId: number, skillId: number): void {
        this.socket.output.clear();
        this.socket.output.writeByte(MsgType.ATTACK);
        this.socket.output.writeInt32(targetId);
        this.socket.output.writeInt32(skillId);
        this.socket.flush();
    }

    private onMessage(data: any): void {
        if (!(data instanceof ArrayBuffer)) return;

        let byte = new Laya.Byte(data);
        byte.endian = Laya.Byte.LITTLE_ENDIAN;

        let msgType = byte.getByte();

        switch (msgType) {
            case MsgType.LOGIN:
                this.handleLogin(byte);
                break;
            case MsgType.MOVE:
                this.handleMove(byte);
                break;
            case MsgType.ATTACK:
                this.handleAttack(byte);
                break;
            case MsgType.SYNC:
                this.handleSync(byte);
                break;
        }
    }

    private handleLogin(byte: Laya.Byte): void {
        let success = byte.getByte() === 1;
        if (success) {
            console.log("登录成功");
        } else {
            console.log("登录失败");
        }
    }

    private handleMove(byte: Laya.Byte): void {
        let playerId = byte.getInt32();
        let x = byte.getFloat32();
        let y = byte.getFloat32();
        console.log(`玩家 ${playerId} 移动到 (${x}, ${y})`);
    }

    private handleAttack(byte: Laya.Byte): void {
        let attackerId = byte.getInt32();
        let targetId = byte.getInt32();
        let damage = byte.getInt32();
        console.log(`玩家 ${attackerId} 攻击 ${targetId},造成 ${damage} 伤害`);
    }

    private handleSync(byte: Laya.Byte): void {
        let count = byte.getByte();
        for (let i = 0; i < count; i++) {
            let id = byte.getInt32();
            let x = byte.getFloat32();
            let y = byte.getFloat32();
            // 同步玩家位置...
        }
    }
}

示例3: 心跳保活机制

typescript 复制代码
@regClass()
export class HeartbeatSocket extends Laya.Script {
    private socket: Laya.Socket;
    private heartbeatInterval: number = 30000;  // 30秒
    private timeoutDelay: number = 5000;  // 5秒超时
    private lastPingTime: number = 0;

    onAwake(): void {
        this.initSocket();
    }

    private initSocket(): void {
        this.socket = new Laya.Socket();
        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.connectByUrl("ws://localhost:8080");
    }

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

    private onMessage(data: any): void {
        if (typeof data === "string") {
            let msg = JSON.parse(data);
            if (msg.type === "pong") {
                this.onPong();
            }
        }
    }

    private startHeartbeat(): void {
        this.stopHeartbeat();
        Laya.timer.loop(
            this.heartbeatInterval,
            this,
            this.sendPing
        );
    }

    private stopHeartbeat(): void {
        Laya.timer.clear(this, this.sendPing);
        Laya.timer.clear(this, this.onTimeout);
    }

    private sendPing(): void {
        this.lastPingTime = Date.now();
        this.socket.send(JSON.stringify({ type: "ping" }));
        Laya.timer.once(this.timeoutDelay, this, this.onTimeout);
    }

    private onPong(): void {
        // 收到响应,清除超时检测
        Laya.timer.clear(this, this.onTimeout);
        let latency = Date.now() - this.lastPingTime;
        console.log(`网络延迟: ${latency}ms`);
    }

    private onTimeout(): void {
        console.warn("心跳超时,连接可能已断开");
        this.socket.close();
    }

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

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

示例4: 断线重连与消息队列

typescript 复制代码
@regClass()
export class ReliableSocket extends Laya.Script {
    private socket: Laya.Socket;
    private messageQueue: string[] = [];
    private isConnecting: boolean = false;
    private reconnectAttempts: number = 0;
    private maxReconnectAttempts: number = 5;

    onAwake(): void {
        this.connect();
    }

    private connect(): void {
        if (this.isConnecting) return;
        this.isConnecting = true;

        this.socket = new Laya.Socket();
        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);

        this.socket.connectByUrl("ws://localhost:8080");
    }

    private onOpen(): void {
        console.log("连接成功");
        this.isConnecting = false;
        this.reconnectAttempts = 0;

        // 发送队列中的消息
        this.flushMessageQueue();
    }

    private onClose(): void {
        console.log("连接关闭");
        this.isConnecting = false;
        this.scheduleReconnect();
    }

    private onError(): void {
        console.log("连接错误");
        this.isConnecting = false;
        this.scheduleReconnect();
    }

    private scheduleReconnect(): void {
        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
            console.error("达到最大重连次数");
            return;
        }

        this.reconnectAttempts++;
        let delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 10000);

        console.log(`${delay}ms 后尝试第 ${this.reconnectAttempts} 次重连`);
        Laya.timer.once(delay, this, this.connect);
    }

    // 发送消息(自动处理断线情况)
    public send(msg: any): void {
        let data = JSON.stringify(msg);

        if (this.socket && this.socket.connected) {
            this.socket.send(data);
        } else {
            console.log("未连接,消息加入队列");
            this.messageQueue.push(data);

            // 如果没有在连接中,开始连接
            if (!this.isConnecting) {
                this.connect();
            }
        }
    }

    private flushMessageQueue(): void {
        while (this.messageQueue.length > 0 && this.socket.connected) {
            let msg = this.messageQueue.shift();
            this.socket.send(msg);
        }
    }

    private onMessage(data: any): void {
        if (typeof data === "string") {
            let msg = JSON.parse(data);
            console.log("收到消息:", msg);
        }
    }

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

示例5: 房间匹配与游戏同步

typescript 复制代码
interface Player {
    id: string;
    name: string;
    x: number;
    y: number;
    hp: number;
}

@regClass()
export class GameNetwork extends Laya.Script {
    private socket: Laya.Socket;
    private players: Map<string, Player> = new Map();
    private localPlayer: Player = null!;

    onAwake(): void {
        this.initSocket();
    }

    private initSocket(): void {
        this.socket = new Laya.Socket();
        this.socket.on(Laya.Event.OPEN, this, this.onOpen);
        this.socket.on(Laya.Event.MESSAGE, this, this.onMessage);
        this.socket.connectByUrl("ws://localhost:8080/game");
    }

    private onOpen(): void {
        console.log("游戏服务器连接成功");
        this.matchRoom();
    }

    // 匹配房间
    public matchRoom(): void {
        this.send({
            type: "match",
            mode: "1v1"
        });
    }

    // 发送移动
    public sendMove(x: number, y: number): void {
        this.send({
            type: "move",
            x: x,
            y: y
        });
    }

    // 发送攻击
    public sendAttack(targetId: string): void {
        this.send({
            type: "attack",
            targetId: targetId
        });
    }

    private send(msg: any): void {
        if (this.socket.connected) {
            this.socket.send(JSON.stringify(msg));
        }
    }

    private onMessage(data: any): void {
        if (typeof data !== "string") return;

        let msg = JSON.parse(data);

        switch (msg.type) {
            case "matched":
                this.onMatched(msg);
                break;
            case "gameStart":
                this.onGameStart(msg);
                break;
            case "playerJoin":
                this.onPlayerJoin(msg);
                break;
            case "playerLeave":
                this.onPlayerLeave(msg);
                break;
            case "playerMove":
                this.onPlayerMove(msg);
                break;
            case "playerAttack":
                this.onPlayerAttack(msg);
                break;
            case "gameState":
                this.onGameState(msg);
                break;
            case "gameOver":
                this.onGameOver(msg);
                break;
        }
    }

    private onMatched(msg: any): void {
        console.log("匹配成功,房间 ID:", msg.roomId);
    }

    private onGameStart(msg: any): void {
        console.log("游戏开始");
        this.localPlayer = {
            id: msg.playerId,
            name: msg.playerName,
            x: msg.x,
            y: msg.y,
            hp: 100
        };
    }

    private onPlayerJoin(msg: any): void {
        let player: Player = {
            id: msg.id,
            name: msg.name,
            x: msg.x,
            y: msg.y,
            hp: 100
        };
        this.players.set(player.id, player);
        console.log(`${player.name} 加入游戏`);
    }

    private onPlayerLeave(msg: any): void {
        this.players.delete(msg.id);
        console.log(`玩家 ${msg.id} 离开游戏`);
    }

    private onPlayerMove(msg: any): void {
        let player = this.players.get(msg.id);
        if (player) {
            player.x = msg.x;
            player.y = msg.y;
        }
    }

    private onPlayerAttack(msg: any): void {
        console.log(`${msg.attackerId} 攻击 ${msg.targetId},伤害 ${msg.damage}`);
        // 播放攻击特效...
    }

    private onGameState(msg: any): void {
        // 全量状态同步
        for (let p of msg.players) {
            let player = this.players.get(p.id) || {
                id: p.id,
                name: p.name,
                x: 0,
                y: 0,
                hp: 100
            };
            player.x = p.x;
            player.y = p.y;
            player.hp = p.hp;
            this.players.set(player.id, player);
        }
    }

    private onGameOver(msg: any): void {
        console.log("游戏结束,结果:", msg.result);
    }

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

高级技巧

1. 协议封包与解包

typescript 复制代码
class Packet {
    static write(type: number, data: any): ArrayBuffer {
        let json = JSON.stringify(data);
        let jsonBytes = new TextEncoder().encode(json);
        let buffer = new ArrayBuffer(3 + jsonBytes.length);
        let view = new DataView(buffer);

        view.setUint8(0, type);           // 消息类型 (1 byte)
        view.setUint16(1, jsonBytes.length);  // 数据长度 (2 bytes)

        for (let i = 0; i < jsonBytes.length; i++) {
            view.setUint8(3 + i, jsonBytes[i]);
        }

        return buffer;
    }

    static read(buffer: ArrayBuffer): { type: number, data: any } {
        let view = new DataView(buffer);
        let type = view.getUint8(0);
        let length = view.getUint16(1);
        let bytes = new Uint8Array(buffer, 3, length);
        let json = new TextDecoder().decode(bytes);
        let data = JSON.parse(json);

        return { type, data };
    }
}

// 使用
let packet = Packet.write(1, { action: "move", x: 100, y: 200 });
socket.send(packet);

// 接收
private onMessage(data: any): void {
    let msg = Packet.read(data);
    console.log(`类型: ${msg.type}, 数据:`, msg.data);
}

2. 消息处理器注册

typescript 复制代码
type MessageHandler = (data: any) => void;

class NetworkDispatcher {
    private handlers: Map<string, MessageHandler> = new Map();
    private socket: Laya.Socket;

    constructor(socket: Laya.Socket) {
        this.socket = socket;
        this.socket.on(Laya.Event.MESSAGE, this, this.onMessage);
    }

    // 注册消息处理器
    on(type: string, handler: MessageHandler): void {
        this.handlers.set(type, handler);
    }

    // 发送消息
    send(type: string, data: any): void {
        this.socket.send(JSON.stringify({ type, ...data }));
    }

    private onMessage(data: any): void {
        if (typeof data !== "string") return;

        let msg = JSON.parse(data);
        let handler = this.handlers.get(msg.type);

        if (handler) {
            handler(msg);
        }
    }

    destroy(): void {
        this.socket.offAll();
        this.handlers.clear();
    }
}

// 使用
let dispatcher = new NetworkDispatcher(socket);

dispatcher.on("login", (data) => {
    console.log("登录响应:", data);
});

dispatcher.on("chat", (data) => {
    console.log(`${data.player}: ${data.message}`);
});

dispatcher.send("login", { userId: "123", token: "xxx" });

3. SSL/TLS 安全连接

typescript 复制代码
// 使用 wss 协议
let socket = new Laya.Socket();

// 方式1: 通过 connect 的 isSecure 参数
socket.connect("secure.example.com", 443, true);

// 方式2: 通过 URL
socket.connectByUrl("wss://secure.example.com:443/ws");

4. 子协议协商

typescript 复制代码
// 指定子协议
let protocols = ["chat", "superchat"];
let socket = new Laya.Socket();
socket.connectByUrl("ws://localhost:8080", protocols);

// 或者使用 connect
socket.connect("localhost", 8080, false, protocols);

5. 处理大文件传输

typescript 复制代码
class FileTransfer {
    private socket: Laya.Socket;
    private chunkSize: number = 1024 * 64;  // 64KB 分片

    constructor(socket: Laya.Socket) {
        this.socket = socket;
    }

    // 发送文件
    sendFile(file: ArrayBuffer, filename: string): Promise<void> {
        return new Promise((resolve, reject) => {
            let totalChunks = Math.ceil(file.byteLength / this.chunkSize);
            let chunkIndex = 0;

            let sendChunk = () => {
                let start = chunkIndex * this.chunkSize;
                let end = Math.min(start + this.chunkSize, file.byteLength);
                let chunk = file.slice(start, end);

                this.socket.send(JSON.stringify({
                    type: "fileChunk",
                    filename: filename,
                    chunkIndex: chunkIndex,
                    totalChunks: totalChunks,
                    data: chunk
                }));

                chunkIndex++;

                if (chunkIndex < totalChunks) {
                    // 流控:等待一点时间再发下一片
                    Laya.timer.once(10, this, sendChunk);
                } else {
                    resolve();
                }
            };

            sendChunk();
        });
    }
}

最佳实践

1. 错误处理与重连

typescript 复制代码
class RobustSocket {
    private socket: Laya.Socket;
    private reconnectDelay: number = 1000;
    private maxReconnectDelay: number = 30000;

    connect(): void {
        this.socket = new Laya.Socket();

        this.socket.on(Laya.Event.ERROR, this, () => {
            this.scheduleReconnect();
        });

        this.socket.on(Laya.Event.CLOSE, this, () => {
            this.scheduleReconnect();
        });

        this.socket.connectByUrl("ws://localhost:8080");
    }

    private scheduleReconnect(): void {
        Laya.timer.once(this.reconnectDelay, this, this.connect);
        this.reconnectDelay = Math.min(
            this.reconnectDelay * 2,
            this.maxReconnectDelay
        );
    }
}

2. 统一的连接管理

typescript 复制代码
@regClass()
export class SocketManager {
    private static instance: SocketManager;
    private socket: Laya.Socket;

    static getInstance(): SocketManager {
        if (!SocketManager.instance) {
            SocketManager.instance = new SocketManager();
        }
        return SocketManager.instance;
    }

    private constructor() {
        this.socket = new Laya.Socket();
    }

    connect(url: string): void {
        this.socket.connectByUrl(url);
    }

    send(type: string, data: any): void {
        this.socket.send(JSON.stringify({ type, ...data }));
    }

    on(type: string, handler: Function): void {
        this.socket.on(type, handler);
    }

    disconnect(): void {
        this.socket.cleanSocket();
    }
}

// 全局使用
let socket = SocketManager.getInstance();
socket.connect("ws://localhost:8080");

3. 消息序列化优化

typescript 复制代码
// 字符串数据禁用二进制转换
this.socket.disableInput = true;

// 二进制数据设置正确的字节序
this.socket.endian = Laya.Byte.LITTLE_ENDIAN;

4. 资源清理

typescript 复制代码
@regClass()
export class GameScene extends Laya.Scene {
    private socket: Laya.Socket;

    onAwake(): void {
        this.socket = new Laya.Socket();
        // ... 初始化
    }

    onDestroy(): void {
        // 必须清理
        this.socket.offAll();
        this.socket.cleanSocket();  // 或 close()
    }
}

5. 连接状态检测

typescript 复制代码
// 发送前检查连接
public send(msg: any): boolean {
    if (!this.socket || !this.socket.connected) {
        console.warn("Socket 未连接");
        return false;
    }
    this.socket.send(JSON.stringify(msg));
    return true;
}

6. 心跳与超时处理

typescript 复制代码
// 定期发送心跳保持连接
Laya.timer.loop(30000, this, () => {
    if (this.socket.connected) {
        this.socket.send(JSON.stringify({ type: "heartbeat" }));
    }
});

测试服务器

完整的 Socket 通信需要服务端和客户端配合。本节提供:

  1. 服务端代码 - Node.js WebSocket 服务器
  2. 客户端代码 - Laya.Socket 连接测试

服务端代码

创建 socket-test-server.js

javascript 复制代码
const WebSocket = require('ws');
const PORT = 8080;
const wss = new WebSocket.Server({ port: PORT });

console.log(`WebSocket 服务器启动在 ws://localhost:${PORT}`);

// 存储所有连接的客户端
const clients = new Map();

wss.on('connection', (ws) => {
    const clientId = Math.random().toString(36).substring(2, 10);
    const clientInfo = {
        id: clientId,
        ws: ws,
        name: `Player_${clientId.substring(0, 4)}`,
        x: 0,
        y: 0,
        hp: 100
    };
    clients.set(clientId, clientInfo);

    // 发送连接成功消息
    ws.send(JSON.stringify({
        type: 'connected',
        clientId: clientId
    }));

    ws.on('message', (data) => {
        let msg = JSON.parse(data);
        handleMessage(clientInfo, msg);
    });

    ws.on('close', () => {
        clients.delete(clientId);
        broadcast({ type: 'playerLeave', id: clientId });
    });
});

function handleMessage(client, msg) {
    console.log(`[${client.name}] ${msg.type}`);

    switch (msg.type) {
        case 'ping':
            client.ws.send(JSON.stringify({ type: 'pong' }));
            break;

        case 'login':
            client.name = msg.userId || client.name;
            client.ws.send(JSON.stringify({
                type: 'login',
                success: true,
                playerId: client.id
            }));
            break;

        case 'move':
            client.x = msg.x;
            client.y = msg.y;
            broadcast({
                type: 'playerMove',
                id: client.id,
                name: client.name,
                x: client.x,
                y: client.y
            }, client.id);
            break;

        case 'chat':
            broadcast({
                type: 'chat',
                playerId: client.id,
                playerName: client.name,
                content: msg.content
            });
            break;

        case 'match':
            setTimeout(() => {
                client.ws.send(JSON.stringify({
                    type: 'matched',
                    roomId: 'room_' + Date.now(),
                    players: Array.from(clients.values()).map(c => ({
                        id: c.id,
                        name: c.name,
                        x: c.x,
                        y: c.y,
                        hp: c.hp
                    }))
                }));
            }, 1000);
            break;
    }
}

function broadcast(data, excludeId) {
    const message = JSON.stringify(data);
    clients.forEach((client, id) => {
        if (id !== excludeId && client.ws.readyState === WebSocket.OPEN) {
            client.ws.send(message);
        }
    });
}

运行服务端

bash 复制代码
npm install ws
node socket-test-server.js

客户端代码 (Laya.Socket)

typescript 复制代码
@regClass()
export class TestSocketClient extends Laya.Script {
    private socket: Laya.Socket;
    private myPlayerId: string = "";

    onAwake(): void {
        this.socket = new Laya.Socket();
        this.socket.disableInput = true;

        // 监听事件
        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);

        // 连接服务器
        this.socket.connectByUrl("ws://localhost:8080");
    }

    // ========== 发送消息 ==========

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

        // 发送登录
        this.sendLogin("player123");

        // 发送聊天
        this.sendChat("Hello Server!");

        // 发送移动
        this.sendMove(100, 200);

        // 请求匹配
        this.sendMatch("1v1");
    }

    private sendLogin(userId: string): void {
        this.socket.send(JSON.stringify({
            type: "login",
            userId: userId
        }));
    }

    private sendChat(content: string): void {
        this.socket.send(JSON.stringify({
            type: "chat",
            content: content
        }));
    }

    private sendMove(x: number, y: number): void {
        this.socket.send(JSON.stringify({
            type: "move",
            x: x,
            y: y
        }));
    }

    private sendMatch(mode: string): void {
        this.socket.send(JSON.stringify({
            type: "match",
            mode: mode
        }));
    }

    private sendPing(): void {
        this.socket.send(JSON.stringify({ type: "ping" }));
    }

    // ========== 接收消息 ==========

    private onMessage(data: any): void {
        let msg = JSON.parse(data);

        switch (msg.type) {
            case "connected":
                this.myPlayerId = msg.clientId;
                console.log("连接成功,我的 ID:", this.myPlayerId);
                break;

            case "login":
                console.log("登录成功,玩家 ID:", msg.playerId);
                break;

            case "playerJoin":
            case "playerLeave":
                console.log(`玩家 ${msg.id} ${msg.type === 'playerJoin' ? '加入' : '离开'}`);
                break;

            case "playerMove":
                console.log(`${msg.name} 移动到 (${msg.x}, ${msg.y})`);
                break;

            case "chat":
                console.log(`${msg.playerName}: ${msg.content}`);
                break;

            case "matched":
                console.log("匹配成功,房间:", msg.roomId);
                console.log("房间内玩家:", msg.players);
                break;

            case "pong":
                console.log("收到心跳响应");
                break;
        }
    }

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

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

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

消息流程图

复制代码
客户端                              服务端
  |                                   |
  |------- connectByUrl --------------->|
  |<----- Event.OPEN -----------------|
  |                                   |
  |------- send login --------------->|
  |                                   |-- 解析 login 消息
  |                                   |-- 生成 playerId
  |<----- login success -------------|
  |                                   |
  |------- send chat ---------------->|
  |                                   |-- 广播给所有客户端
  |<----- chat message --------------|
  |                                   |
  |------- send match --------------->|
  |                                   |-- 延迟 1 秒
  |                                   |-- 收集在线玩家
  |<----- matched result ------------|
  |                                   |

服务端控制台输出

复制代码
WebSocket 服务器启动在 ws://localhost:8080
[10:30:15] 客户端连接: a1b2c3d4,当前在线: 1
[10:30:15] a1b2c3d4 -> login
[10:30:15] 玩家登录: player123
[10:30:16] a1b2c3d4 -> chat
[10:30:16] Player_a1b2: Hello Server!
[10:30:17] a1b2c3d4 -> move
[10:30:18] a1b2c3d4 -> match
[10:30:18] 客户端断开: a1b2c3d4,当前在线: 0

注意事项

  1. 必须监听事件:使用前必须监听 OPEN、MESSAGE、CLOSE、ERROR 事件
  2. 连接后发送:确保连接成功(OPEN 事件)后再发送数据
  3. 字节序一致性:客户端和服务器必须使用相同的字节序
  4. 清理资源 :场景销毁时调用 cleanSocket()close()
  5. HTTPS 限制:在 HTTPS 页面中必须使用 wss 协议
  6. 字符串优化 :传输字符串时设置 disableInput = true 减少二进制转换
  7. 缓冲区清理 :接收完数据后及时调用 input.clear() 释放内存

相关文档

相关推荐
We་ct3 小时前
LeetCode 125. 验证回文串:双指针解法全解析与优化
前端·算法·leetcode·typescript
tealcwu5 小时前
【Unity资源】Unity MCP 介绍
unity·游戏引擎
We་ct5 小时前
LeetCode 68. 文本左右对齐:贪心算法的两种实现与深度解析
前端·算法·leetcode·typescript
EndingCoder7 小时前
设计模式在 TypeScript 中的实现
前端·typescript
晚霞的不甘8 小时前
Flutter for OpenHarmony 引力弹球游戏开发全解析:从零构建一个交互式物理小游戏
前端·flutter·云原生·前端框架·游戏引擎·harmonyos·骨骼绑定
奔跑的web.8 小时前
TypeScript namespace 详解:语法用法与使用建议
开发语言·前端·javascript·vue.js·typescript
鱼跃鹰飞9 小时前
面试题:知道WebSocket协议吗?
网络·websocket·网络协议
Thomas_YXQ9 小时前
Unity3D中提升AssetBundle加载速度的详细指南
java·spring boot·spring·unity·性能优化·游戏引擎·游戏开发
michael_ouyang10 小时前
IM 消息收发流程方案选型
前端·websocket·网络协议·typescript·electron