http基于websocket协议通信

基础概念

WebSocket 和 TCP/IP 模型

WebSocket 是一个在单个 TCP 连接上进行全双工通信的协议。它建立在 HTTP 协议之上,通过 HTTP 握手升级为 WebSocket 协议。

应用层 - WebSocket协议
传输层 - TCP协议
网络层 - IP协议
数据链路层 - 以太网协议
物理层 - 网络硬件

WebSocket 在 TCP/IP 模型中的位置:

  • 应用层:WebSocket 协议提供应用程序接口
  • 传输层:基于可靠的 TCP 连接
  • 网络层:使用 IP 协议进行路由
  • 数据链路层和物理层:处理底层网络传输

WebSocket 的工作原理

WebSocket 协议的核心特性:

  1. 全双工通信:客户端和服务端可以同时发送数据
  2. 持久连接:连接建立后保持开放状态
  3. 低开销:相比 HTTP 轮询,减少了不必要的头部信息
  4. 实时性:数据可以即时传输,无需等待
客户端服务端HTTP 握手阶段HTTP Upgrade RequestHTTP 101 Switching ProtocolsWebSocket 通信阶段WebSocket Frame (数据)WebSocket Frame (数据)WebSocket Frame (数据)WebSocket Frame (数据)连接关闭Close FrameClose Frame客户端服务端

WebSocket 握手详解

WebSocket 握手是一个标准的 HTTP 升级请求,用于将 HTTP 连接升级为 WebSocket 连接。

请求(客户端发起的握手请求)

客户端发送的 HTTP 请求包含特定的头部字段:

// WebSocket 握手请求示例
const handshakeRequest = `GET /websocket HTTP/1.1
Host: example.com:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: chat, superchat
Origin: http://example.com`;

// 关键头部字段解析
const headers = {
    'Upgrade': 'websocket',           // 表示要升级到 WebSocket
    'Connection': 'Upgrade',          // 表示连接升级
    'Sec-WebSocket-Key': 'base64编码的随机值', // 用于验证握手
    'Sec-WebSocket-Version': '13',    // WebSocket 协议版本
    'Sec-WebSocket-Protocol': 'chat', // 可选的子协议
    'Origin': 'http://example.com'    // 请求来源
};
响应(服务器端的握手响应)

服务端验证请求后,返回 101 状态码表示协议切换:

// WebSocket 握手响应示例
const handshakeResponse = `HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat`;

// Sec-WebSocket-Accept 计算方法
const crypto = require('crypto');

function generateAcceptKey(clientKey) {
    const magicString = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
    const concatenated = clientKey + magicString;
    const sha1Hash = crypto.createHash('sha1').update(concatenated).digest();
    return sha1Hash.toString('base64');
}

// 示例计算
const clientKey = 'dGhlIHNhbXBsZSBub25jZQ==';
const acceptKey = generateAcceptKey(clientKey);
console.log('Sec-WebSocket-Accept:', acceptKey);

实现 WebSocket 握手和通信

创建 HTTP 服务器并实现 WebSocket

const http = require('http');
const crypto = require('crypto');
const { EventEmitter } = require('events');

class WebSocketServer extends EventEmitter {
    constructor(port) {
        super();
        this.port = port;
        this.clients = new Set();
        this.server = http.createServer();
        this.setupServer();
    }

    setupServer() {
        this.server.on('upgrade', (request, socket, head) => {
            this.handleUpgrade(request, socket, head);
        });

        this.server.listen(this.port, () => {
            console.log(`WebSocket服务器启动在端口 ${this.port}`);
        });
    }

    handleUpgrade(request, socket, head) {
        const key = request.headers['sec-websocket-key'];
        if (!key) {
            socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
            return;
        }

        const acceptKey = this.generateAcceptKey(key);
        const responseHeaders = [
            'HTTP/1.1 101 Switching Protocols',
            'Upgrade: websocket',
            'Connection: Upgrade',
            `Sec-WebSocket-Accept: ${acceptKey}`,
            '\r\n'
        ].join('\r\n');

        socket.write(responseHeaders);
        
        // 创建 WebSocket 连接实例
        const wsConnection = new WebSocketConnection(socket);
        this.clients.add(wsConnection);
        
        wsConnection.on('message', (data) => {
            this.emit('message', wsConnection, data);
        });

        wsConnection.on('close', () => {
            this.clients.delete(wsConnection);
            this.emit('close', wsConnection);
        });

        this.emit('connection', wsConnection);
    }

    generateAcceptKey(clientKey) {
        const magicString = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
        const concatenated = clientKey + magicString;
        return crypto.createHash('sha1').update(concatenated).digest('base64');
    }

    broadcast(message) {
        this.clients.forEach(client => {
            client.send(message);
        });
    }
}

握手解析

class HandshakeParser {
    static parseRequest(request) {
        const headers = request.headers;
        
        return {
            method: request.method,
            url: request.url,
            httpVersion: request.httpVersion,
            upgrade: headers.upgrade,
            connection: headers.connection,
            secWebSocketKey: headers['sec-websocket-key'],
            secWebSocketVersion: headers['sec-websocket-version'],
            secWebSocketProtocol: headers['sec-websocket-protocol'],
            origin: headers.origin,
            userAgent: headers['user-agent']
        };
    }

    static validateHandshake(parsedRequest) {
        const errors = [];

        if (parsedRequest.method !== 'GET') {
            errors.push('请求方法必须是 GET');
        }

        if (parsedRequest.upgrade !== 'websocket') {
            errors.push('Upgrade 头部必须是 websocket');
        }

        if (!parsedRequest.connection.includes('Upgrade')) {
            errors.push('Connection 头部必须包含 Upgrade');
        }

        if (!parsedRequest.secWebSocketKey) {
            errors.push('缺少 Sec-WebSocket-Key 头部');
        }

        if (parsedRequest.secWebSocketVersion !== '13') {
            errors.push('不支持的 WebSocket 版本');
        }

        return {
            isValid: errors.length === 0,
            errors
        };
    }
}

WebSocket 数据帧格式

WebSocket 数据传输使用特定的帧格式:

WebSocket 数据帧
FIN: 1位 - 是否最后一帧
RSV: 3位 - 保留字段
Opcode: 4位 - 操作码
MASK: 1位 - 是否掩码
Payload Length: 7位/16位/64位
Masking Key: 0或4字节
Payload Data: 可变长度
// WebSocket 帧类型常量
const OPCODES = {
    CONTINUATION: 0x0,
    TEXT: 0x1,
    BINARY: 0x2,
    CLOSE: 0x8,
    PING: 0x9,
    PONG: 0xa
};

class WebSocketFrame {
    constructor(data) {
        this.buffer = data;
        this.parse();
    }

    parse() {
        const firstByte = this.buffer[0];
        const secondByte = this.buffer[1];

        this.fin = (firstByte & 0x80) === 0x80;
        this.rsv1 = (firstByte & 0x40) === 0x40;
        this.rsv2 = (firstByte & 0x20) === 0x20;
        this.rsv3 = (firstByte & 0x10) === 0x10;
        this.opcode = firstByte & 0x0f;

        this.masked = (secondByte & 0x80) === 0x80;
        this.payloadLength = secondByte & 0x7f;

        let offset = 2;

        // 扩展载荷长度
        if (this.payloadLength === 126) {
            this.payloadLength = this.buffer.readUInt16BE(offset);
            offset += 2;
        } else if (this.payloadLength === 127) {
            this.payloadLength = this.buffer.readBigUInt64BE(offset);
            offset += 8;
        }

        // 掩码键
        if (this.masked) {
            this.maskingKey = this.buffer.slice(offset, offset + 4);
            offset += 4;
        }

        // 载荷数据
        this.payload = this.buffer.slice(offset);
        
        if (this.masked) {
            this.unmaskPayload();
        }
    }

    unmaskPayload() {
        for (let i = 0; i < this.payload.length; i++) {
            this.payload[i] ^= this.maskingKey[i % 4];
        }
    }

    static createFrame(opcode, payload, masked = false) {
        const payloadLength = Buffer.byteLength(payload);
        let frame;
        let offset = 0;

        // 计算帧头长度
        let headerLength = 2;
        if (payloadLength > 65535) {
            headerLength += 8;
        } else if (payloadLength > 125) {
            headerLength += 2;
        }
        
        if (masked) {
            headerLength += 4;
        }

        frame = Buffer.allocUnsafe(headerLength + payloadLength);

        // 第一字节:FIN(1) + RSV(3) + Opcode(4)
        frame[0] = 0x80 | opcode;

        // 第二字节:MASK(1) + Payload Length(7)
        if (payloadLength > 65535) {
            frame[1] = masked ? 0xff : 0x7f;
            frame.writeBigUInt64BE(BigInt(payloadLength), 2);
            offset = 10;
        } else if (payloadLength > 125) {
            frame[1] = masked ? 0xfe : 0x7e;
            frame.writeUInt16BE(payloadLength, 2);
            offset = 4;
        } else {
            frame[1] = masked ? (0x80 | payloadLength) : payloadLength;
            offset = 2;
        }

        // 掩码键(如果需要)
        if (masked) {
            const maskingKey = crypto.randomBytes(4);
            maskingKey.copy(frame, offset);
            offset += 4;

            // 应用掩码到载荷数据
            const payloadBuffer = Buffer.from(payload);
            for (let i = 0; i < payloadBuffer.length; i++) {
                payloadBuffer[i] ^= maskingKey[i % 4];
            }
            payloadBuffer.copy(frame, offset);
        } else {
            Buffer.from(payload).copy(frame, offset);
        }

        return frame;
    }
}

处理 WebSocket 帧

class WebSocketConnection extends EventEmitter {
    constructor(socket) {
        super();
        this.socket = socket;
        this.buffer = Buffer.alloc(0);
        this.setupSocket();
    }

    setupSocket() {
        this.socket.on('data', (data) => {
            this.handleData(data);
        });

        this.socket.on('close', () => {
            this.emit('close');
        });

        this.socket.on('error', (error) => {
            this.emit('error', error);
        });
    }

    handleData(data) {
        this.buffer = Buffer.concat([this.buffer, data]);

        while (this.buffer.length >= 2) {
            try {
                const frame = new WebSocketFrame(this.buffer);
                
                // 检查是否有完整的帧
                const frameSize = this.calculateFrameSize(frame);
                if (this.buffer.length < frameSize) {
                    break; // 等待更多数据
                }

                // 处理帧
                this.processFrame(frame);
                
                // 移除已处理的数据
                this.buffer = this.buffer.slice(frameSize);
            } catch (error) {
                this.emit('error', error);
                break;
            }
        }
    }

    calculateFrameSize(frame) {
        let size = 2; // 基本头部
        
        if (frame.payloadLength > 65535) {
            size += 8;
        } else if (frame.payloadLength > 125) {
            size += 2;
        }
        
        if (frame.masked) {
            size += 4;
        }
        
        size += frame.payloadLength;
        return size;
    }

    processFrame(frame) {
        switch (frame.opcode) {
            case OPCODES.TEXT:
                const text = frame.payload.toString('utf8');
                this.emit('message', text);
                break;
                
            case OPCODES.BINARY:
                this.emit('message', frame.payload);
                break;
                
            case OPCODES.CLOSE:
                this.close();
                break;
                
            case OPCODES.PING:
                this.pong(frame.payload);
                break;
                
            case OPCODES.PONG:
                this.emit('pong', frame.payload);
                break;
        }
    }

    send(data) {
        const opcode = typeof data === 'string' ? OPCODES.TEXT : OPCODES.BINARY;
        const frame = WebSocketFrame.createFrame(opcode, data);
        this.socket.write(frame);
    }

    ping(data = Buffer.alloc(0)) {
        const frame = WebSocketFrame.createFrame(OPCODES.PING, data);
        this.socket.write(frame);
    }

    pong(data = Buffer.alloc(0)) {
        const frame = WebSocketFrame.createFrame(OPCODES.PONG, data);
        this.socket.write(frame);
    }

    close() {
        const frame = WebSocketFrame.createFrame(OPCODES.CLOSE, '');
        this.socket.write(frame);
        this.socket.end();
    }
}

socket.io

socket.io 的优势

Socket.IO 是一个基于 WebSocket 的实时通信库,提供了许多增强功能:

  1. 自动降级:当 WebSocket 不可用时,自动回退到轮询
  2. 房间和命名空间:逻辑分组和隔离
  3. 自动重连:连接断开时自动重连
  4. 二进制支持:支持传输二进制数据
  5. 跨平台:支持多种客户端平台
是
否
是
否
Socket.IO 连接尝试
WebSocket 支持?
WebSocket 连接
HTTP 长轮询
实时双向通信
连接断开?
自动重连机制
持续通信

基本实现:socket.io 服务端与客户端

安装 socket.io
// 服务端安装
npm install socket.io

// 客户端安装
npm install socket.io-client
创建 socket.io 服务端
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
    cors: {
        origin: "*",
        methods: ["GET", "POST"]
    },
    transports: ['websocket', 'polling']
});

// 中间件:用户认证
io.use((socket, next) => {
    const token = socket.handshake.auth.token;
    if (token) {
        // 验证token逻辑
        socket.userId = verifyToken(token);
        next();
    } else {
        next(new Error('Authentication error'));
    }
});

// 连接事件处理
io.on('connection', (socket) => {
    console.log(`用户 ${socket.userId} 连接成功`);

    // 加入用户房间
    socket.join(`user_${socket.userId}`);

    // 监听客户端消息
    socket.on('message', (data) => {
        console.log('收到消息:', data);
        
        // 广播给所有客户端
        socket.broadcast.emit('message', {
            userId: socket.userId,
            message: data.message,
            timestamp: Date.now()
        });
    });

    // 私聊功能
    socket.on('private_message', (data) => {
        const { targetUserId, message } = data;
        socket.to(`user_${targetUserId}`).emit('private_message', {
            from: socket.userId,
            message,
            timestamp: Date.now()
        });
    });

    // 实时位置共享
    socket.on('location_update', (data) => {
        socket.broadcast.emit('user_location', {
            userId: socket.userId,
            latitude: data.latitude,
            longitude: data.longitude,
            timestamp: Date.now()
        });
    });

    // 断开连接
    socket.on('disconnect', (reason) => {
        console.log(`用户 ${socket.userId} 断开连接: ${reason}`);
        socket.broadcast.emit('user_offline', socket.userId);
    });

    // 错误处理
    socket.on('error', (error) => {
        console.error('Socket错误:', error);
    });
});

function verifyToken(token) {
    // 简化的token验证
    return token === 'valid_token' ? 'user123' : null;
}

server.listen(3000, () => {
    console.log('Socket.IO服务器运行在端口3000');
});
创建 socket.io 客户端
// Node.js 客户端
const io = require('socket.io-client');

class SocketClient {
    constructor(url, options = {}) {
        this.url = url;
        this.socket = null;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
        this.reconnectDelay = options.reconnectDelay || 1000;
    }

    connect(auth = {}) {
        this.socket = io(this.url, {
            auth,
            transports: ['websocket', 'polling'],
            timeout: 5000,
            autoConnect: true
        });

        this.setupEventListeners();
        return this.socket;
    }

    setupEventListeners() {
        // 连接成功
        this.socket.on('connect', () => {
            console.log('连接成功,Socket ID:', this.socket.id);
            this.reconnectAttempts = 0;
        });

        // 连接失败
        this.socket.on('connect_error', (error) => {
            console.error('连接失败:', error.message);
            this.handleReconnect();
        });

        // 断开连接
        this.socket.on('disconnect', (reason) => {
            console.log('连接断开:', reason);
            if (reason === 'io server disconnect') {
                // 服务器主动断开,需要重连
                this.socket.connect();
            }
        });

        // 接收消息
        this.socket.on('message', (data) => {
            console.log('收到消息:', data);
            this.handleMessage(data);
        });

        // 私聊消息
        this.socket.on('private_message', (data) => {
            console.log('私聊消息:', data);
            this.handlePrivateMessage(data);
        });

        // 位置更新
        this.socket.on('user_location', (data) => {
            console.log('用户位置:', data);
            this.handleLocationUpdate(data);
        });
    }

    // 发送消息
    sendMessage(message) {
        if (this.socket && this.socket.connected) {
            this.socket.emit('message', { message });
        } else {
            console.error('Socket未连接');
        }
    }

    // 发送私聊
    sendPrivateMessage(targetUserId, message) {
        if (this.socket && this.socket.connected) {
            this.socket.emit('private_message', { targetUserId, message });
        }
    }

    // 更新位置
    updateLocation(latitude, longitude) {
        if (this.socket && this.socket.connected) {
            this.socket.emit('location_update', { latitude, longitude });
        }
    }

    handleReconnect() {
        if (this.reconnectAttempts < this.maxReconnectAttempts) {
            this.reconnectAttempts++;
            const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
            
            console.log(`${delay}ms后尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
            
            setTimeout(() => {
                this.socket.connect();
            }, delay);
        } else {
            console.error('达到最大重连次数,停止重连');
        }
    }

    handleMessage(data) {
        // 处理普通消息的逻辑
    }

    handlePrivateMessage(data) {
        // 处理私聊消息的逻辑
    }

    handleLocationUpdate(data) {
        // 处理位置更新的逻辑
    }

    disconnect() {
        if (this.socket) {
            this.socket.disconnect();
        }
    }
}

// 使用示例
const client = new SocketClient('http://localhost:3000');
const socket = client.connect({ token: 'valid_token' });

// 发送消息示例
setTimeout(() => {
    client.sendMessage('Hello from client!');
}, 1000);
命名空间和房间
使用命名空间

命名空间允许将 Socket.IO 应用分割为多个独立的通信通道:

// 服务端命名空间实现
const chatNamespace = io.of('/chat');
const gameNamespace = io.of('/game');

// 聊天命名空间
chatNamespace.on('connection', (socket) => {
    console.log('用户连接到聊天命名空间');
    
    socket.on('join_channel', (channel) => {
        socket.join(channel);
        socket.to(channel).emit('user_joined', {
            userId: socket.userId,
            channel
        });
    });

    socket.on('channel_message', (data) => {
        const { channel, message } = data;
        socket.to(channel).emit('channel_message', {
            userId: socket.userId,
            message,
            timestamp: Date.now()
        });
    });
});

// 游戏命名空间
gameNamespace.on('connection', (socket) => {
    console.log('用户连接到游戏命名空间');
    
    socket.on('join_room', (roomId) => {
        socket.join(roomId);
        const room = gameNamespace.adapter.rooms.get(roomId);
        
        if (room && room.size >= 2) {
            gameNamespace.to(roomId).emit('game_start', {
                players: Array.from(room),
                timestamp: Date.now()
            });
        }
    });

    socket.on('game_move', (data) => {
        const { roomId, move } = data;
        socket.to(roomId).emit('opponent_move', {
            playerId: socket.id,
            move,
            timestamp: Date.now()
        });
    });
});

// 客户端连接不同命名空间
const chatSocket = io('http://localhost:3000/chat');
const gameSocket = io('http://localhost:3000/game');
使用房间

房间是 Socket.IO 中用于分组连接的机制:

class RoomManager {
    constructor(io) {
        this.io = io;
        this.rooms = new Map();
    }

    createRoom(roomId, options = {}) {
        const room = {
            id: roomId,
            name: options.name || roomId,
            maxUsers: options.maxUsers || 10,
            isPrivate: options.isPrivate || false,
            users: new Set(),
            createdAt: Date.now()
        };
        
        this.rooms.set(roomId, room);
        return room;
    }

    joinRoom(socket, roomId) {
        const room = this.rooms.get(roomId);
        
        if (!room) {
            socket.emit('room_error', { message: '房间不存在' });
            return false;
        }

        if (room.users.size >= room.maxUsers) {
            socket.emit('room_error', { message: '房间已满' });
            return false;
        }

        // 加入Socket.IO房间
        socket.join(roomId);
        room.users.add(socket.id);

        // 通知房间内其他用户
        socket.to(roomId).emit('user_joined_room', {
            userId: socket.userId,
            roomId,
            userCount: room.users.size
        });

        // 发送房间信息给新用户
        socket.emit('room_joined', {
            room: {
                id: room.id,
                name: room.name,
                userCount: room.users.size,
                users: Array.from(room.users)
            }
        });

        return true;
    }

    leaveRoom(socket, roomId) {
        const room = this.rooms.get(roomId);
        
        if (room) {
            socket.leave(roomId);
            room.users.delete(socket.id);

            // 通知房间内其他用户
            socket.to(roomId).emit('user_left_room', {
                userId: socket.userId,
                roomId,
                userCount: room.users.size
            });

            // 如果房间为空,删除房间
            if (room.users.size === 0) {
                this.rooms.delete(roomId);
            }
        }
    }

    broadcastToRoom(roomId, event, data) {
        this.io.to(roomId).emit(event, data);
    }

    getRoomList() {
        return Array.from(this.rooms.values()).map(room => ({
            id: room.id,
            name: room.name,
            userCount: room.users.size,
            maxUsers: room.maxUsers,
            isPrivate: room.isPrivate
        }));
    }
}

// 使用房间管理器
const roomManager = new RoomManager(io);

io.on('connection', (socket) => {
    // 获取房间列表
    socket.on('get_rooms', () => {
        socket.emit('room_list', roomManager.getRoomList());
    });

    // 创建房间
    socket.on('create_room', (data) => {
        const room = roomManager.createRoom(data.roomId, data.options);
        roomManager.joinRoom(socket, room.id);
    });

    // 加入房间
    socket.on('join_room', (roomId) => {
        roomManager.joinRoom(socket, roomId);
    });

    // 离开房间
    socket.on('leave_room', (roomId) => {
        roomManager.leaveRoom(socket, roomId);
    });

    // 房间消息
    socket.on('room_message', (data) => {
        const { roomId, message } = data;
        socket.to(roomId).emit('room_message', {
            userId: socket.userId,
            message,
            timestamp: Date.now()
        });
    });
});

深入探讨

socket.io 与 WebSocket 的区别
简单实时
复杂场景
实时通信需求
选择协议
原生WebSocket
Socket.IO
优势:
- 轻量级
- 标准协议
- 性能好
劣势:
- 无自动重连
- 无房间概念
- 兼容性问题
优势:
- 自动降级
- 房间功能
- 自动重连
- 跨平台
劣势:
- 体积大
- 性能开销
- 学习成本
特性WebSocketSocket.IO
协议WebSocket协议基于WebSocket + 轮询
浏览器兼容现代浏览器广泛兼容
自动重连需手动实现内置支持
房间功能需手动实现内置支持
命名空间需手动实现内置支持
二进制数据原生支持需要额外处理
性能更高略低(有额外开销)
学习曲线较陡峭相对平缓
使用场景

WebSocket 适用场景:

  • 高频数据传输(如游戏、交易系统)
  • 对延迟要求极高的应用
  • 二进制数据传输
  • 简单的实时通信需求
// WebSocket 适用场景示例:实时股价推送
class StockPriceStreamer {
    constructor(wsUrl) {
        this.ws = new WebSocket(wsUrl);
        this.setupWebSocket();
    }

    setupWebSocket() {
        this.ws.onopen = () => {
            console.log('股价数据流连接成功');
            // 订阅股票代码
            this.ws.send(JSON.stringify({
                action: 'subscribe',
                symbols: ['AAPL', 'GOOGL', 'TSLA']
            }));
        };

        this.ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            this.updateStockPrice(data);
        };

        this.ws.onerror = (error) => {
            console.error('WebSocket错误:', error);
        };
    }

    updateStockPrice(data) {
        // 高频更新UI
        document.getElementById(data.symbol).textContent = data.price;
    }
}

Socket.IO 适用场景:

  • 聊天应用
  • 协作编辑工具
  • 实时通知系统
  • 多人在线游戏
  • 实时监控面板
// Socket.IO 适用场景示例:协作编辑
class CollaborativeEditor {
    constructor() {
        this.socket = io('/editor');
        this.documentId = this.getDocumentId();
        this.setupSocketEvents();
    }

    setupSocketEvents() {
        // 加入文档编辑房间
        this.socket.emit('join_document', this.documentId);

        // 接收其他用户的编辑操作
        this.socket.on('text_change', (data) => {
            this.applyTextChange(data);
        });

        // 用户光标位置同步
        this.socket.on('cursor_position', (data) => {
            this.updateUserCursor(data);
        });

        // 用户加入/离开通知
        this.socket.on('user_joined', (user) => {
            this.showUserJoined(user);
        });

        this.socket.on('user_left', (user) => {
            this.showUserLeft(user);
        });
    }

    handleTextChange(change) {
        // 发送编辑操作给其他用户
        this.socket.emit('text_change', {
            documentId: this.documentId,
            change: change,
            timestamp: Date.now()
        });
    }

    applyTextChange(data) {
        // 应用远程编辑操作
        this.editor.applyOperation(data.change);
    }

    updateUserCursor(data) {
        // 更新其他用户光标位置
        this.cursorManager.updateCursor(data.userId, data.position);
    }

    getDocumentId() {
        return window.location.pathname.split('/').pop();
    }
}

更多推荐