http基于websocket协议通信分析

http基于websocket协议通信

基础概念

WebSocket 和 TCP/IP 模型

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

graph TD A[应用层 - WebSocket协议] --> B[传输层 - TCP协议] B --> C[网络层 - IP协议] C --> D[数据链路层 - 以太网协议] D --> E[物理层 - 网络硬件] style A fill:#e3f2fd style B fill:#e8f5e8 style C fill:#fff3e0 style D fill:#f3e5f5 style E fill:#fce4ec

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

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

WebSocket 的工作原理

WebSocket 协议的核心特性:

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

WebSocket 握手详解

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

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

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

javascript 复制代码
// 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 状态码表示协议切换:

javascript 复制代码
// 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

javascript 复制代码
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);
        });
    }
}

握手解析

javascript 复制代码
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 数据传输使用特定的帧格式:

graph TD A[WebSocket 数据帧] --> B[FIN: 1位 - 是否最后一帧] A --> C[RSV: 3位 - 保留字段] A --> D[Opcode: 4位 - 操作码] A --> E[MASK: 1位 - 是否掩码] A --> F[Payload Length: 7位/16位/64位] A --> G[Masking Key: 0或4字节] A --> H[Payload Data: 可变长度] style A fill:#e3f2fd style B fill:#e8f5e8 style C fill:#fff3e0 style D fill:#f3e5f5
javascript 复制代码
// 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 帧

javascript 复制代码
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. 跨平台:支持多种客户端平台
graph TD A[Socket.IO 连接尝试] --> B{WebSocket 支持?} B -->|是| C[WebSocket 连接] B -->|否| D[HTTP 长轮询] C --> E[实时双向通信] D --> E E --> F{连接断开?} F -->|是| G[自动重连机制] F -->|否| H[持续通信] G --> A style A fill:#e3f2fd style C fill:#e8f5e8 style D fill:#fff3e0 style E fill:#e8f5e8

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

安装 socket.io
javascript 复制代码
// 服务端安装
npm install socket.io

// 客户端安装
npm install socket.io-client
创建 socket.io 服务端
javascript 复制代码
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 客户端
javascript 复制代码
// 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 应用分割为多个独立的通信通道:

javascript 复制代码
// 服务端命名空间实现
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 中用于分组连接的机制:

javascript 复制代码
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 的区别
graph TD A[实时通信需求] --> B{选择协议} B -->|简单实时| C[原生WebSocket] B -->|复杂场景| D[Socket.IO] C --> E[优势:
- 轻量级
- 标准协议
- 性能好] C --> F[劣势:
- 无自动重连
- 无房间概念
- 兼容性问题] D --> G[优势:
- 自动降级
- 房间功能
- 自动重连
- 跨平台] D --> H[劣势:
- 体积大
- 性能开销
- 学习成本] style C fill:#e8f5e8 style D fill:#e3f2fd
特性 WebSocket Socket.IO
协议 WebSocket协议 基于WebSocket + 轮询
浏览器兼容 现代浏览器 广泛兼容
自动重连 需手动实现 内置支持
房间功能 需手动实现 内置支持
命名空间 需手动实现 内置支持
二进制数据 原生支持 需要额外处理
性能 更高 略低(有额外开销)
学习曲线 较陡峭 相对平缓
使用场景

WebSocket 适用场景:

  • 高频数据传输(如游戏、交易系统)
  • 对延迟要求极高的应用
  • 二进制数据传输
  • 简单的实时通信需求
javascript 复制代码
// 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 适用场景:

  • 聊天应用
  • 协作编辑工具
  • 实时通知系统
  • 多人在线游戏
  • 实时监控面板
javascript 复制代码
// 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();
    }
}
相关推荐
chinahcp200816 分钟前
CSS保持元素宽高比,固定元素宽高比
前端·css·html·css3·html5
gnip1 小时前
浏览器跨标签页通信方案详解
前端·javascript
gnip2 小时前
运行时模块批量导入
前端·javascript
hyy27952276842 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
逆风优雅2 小时前
vue实现模拟 ai 对话功能
前端·javascript·html
不羁。。3 小时前
【web站点安全开发】任务3:网页开发的骨架HTML与美容术CSS
前端·css·html
这是个栗子3 小时前
【问题解决】Vue调试工具Vue Devtools插件安装后不显示
前端·javascript·vue.js
姑苏洛言3 小时前
待办事项小程序开发
前端·javascript
百万蹄蹄向前冲3 小时前
让AI写2D格斗游戏,坏了我成测试了
前端·canvas·trae