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 协议的核心特性:
- 全双工通信:客户端和服务端可以同时发送数据
- 持久连接:连接建立后保持开放状态
- 低开销:相比 HTTP 轮询,减少了不必要的头部信息
- 实时性:数据可以即时传输,无需等待
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 的实时通信库,提供了许多增强功能:
- 自动降级:当 WebSocket 不可用时,自动回退到轮询
- 房间和命名空间:逻辑分组和隔离
- 自动重连:连接断开时自动重连
- 二进制支持:支持传输二进制数据
- 跨平台:支持多种客户端平台
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
- 轻量级
- 标准协议
- 性能好] 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();
}
}