Socket.IO 事件系统 #
事件基础 #
Socket.IO 的核心是事件驱动模型,通过 emit 发送事件,通过 on 监听事件。
事件模型 #
text
┌─────────────────────────────────────────────────────────────┐
│ Socket.IO 事件模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ 客户端 │ │ 服务端 │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ │ socket.emit('event', data) │
│ │─────────────────────────>│ │
│ │ │ │
│ │ │ socket.on('event', cb) │
│ │ │ │
│ │ socket.on('reply', cb) │ │
│ │<─────────────────────────│ │
│ │ │ │
│ │ │ socket.emit('reply') │
│ │
└─────────────────────────────────────────────────────────────┘
发送事件 #
基本发送 #
javascript
socket.emit('my event', 'Hello World');
socket.emit('user joined', {
username: 'John',
room: 'general'
});
socket.emit('position', 100, 200);
服务端发送 #
javascript
io.on('connection', (socket) => {
socket.emit('welcome', 'Welcome to the server!');
socket.emit('user info', {
id: socket.id,
connectedAt: new Date()
});
});
广播发送 #
javascript
io.on('connection', (socket) => {
socket.broadcast.emit('user connected', 'A user connected');
io.emit('announcement', 'Server will restart in 5 minutes');
io.to('room-1').emit('room message', 'Hello room 1');
});
接收事件 #
基本监听 #
javascript
socket.on('my event', (data) => {
console.log('收到数据:', data);
});
socket.on('user joined', (data) => {
console.log(`${data.username} 加入了 ${data.room}`);
});
socket.on('position', (x, y) => {
console.log(`位置: (${x}, ${y})`);
});
服务端监听 #
javascript
io.on('connection', (socket) => {
socket.on('chat message', (msg) => {
console.log('消息:', msg);
io.emit('chat message', msg);
});
socket.on('join room', (roomName) => {
socket.join(roomName);
socket.emit('joined', roomName);
});
});
监听所有事件 #
javascript
socket.onAny((eventName, ...args) => {
console.log(`收到事件: ${eventName}`);
console.log('参数:', args);
});
socket.prependAny((eventName, ...args) => {
console.log(`预处理事件: ${eventName}`);
});
移除监听 #
javascript
const handler = (data) => {
console.log('处理数据:', data);
};
socket.on('my event', handler);
socket.off('my event', handler);
socket.off('my event');
socket.removeAllListeners();
消息确认 #
基本确认 #
javascript
socket.emit('update', { name: 'John' }, (response) => {
console.log('服务端确认:', response);
});
socket.on('update', (data, callback) => {
console.log('收到更新:', data);
callback({ status: 'success', timestamp: Date.now() });
});
确认流程 #
text
┌─────────────────────────────────────────────────────────────┐
│ 消息确认流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ 客户端 │ │ 服务端 │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ │ emit('update', data, cb) │ │
│ │─────────────────────────>│ │
│ │ │ │
│ │ │ 处理数据 │
│ │ │ │
│ │ │ 执行 callback(response) │
│ │<─────────────────────────│ │
│ │ │ │
│ │ cb(response) 被调用 │ │
│ │
└─────────────────────────────────────────────────────────────┘
多参数确认 #
javascript
socket.emit('request', { id: 123 }, (status, data) => {
console.log('状态:', status);
console.log('数据:', data);
});
socket.on('request', (data, callback) => {
callback('ok', { result: 'success' });
});
超时处理 #
javascript
socket.timeout(5000).emit('my event', data, (err, response) => {
if (err) {
console.log('确认超时');
} else {
console.log('确认成功:', response);
}
});
服务端确认 #
javascript
io.on('connection', (socket) => {
socket.timeout(5000).emit('request', data, (err, response) => {
if (err) {
console.log('客户端未响应');
} else {
console.log('客户端响应:', response);
}
});
});
保留事件 #
Socket.IO 有一些保留事件,用于处理连接状态。
客户端保留事件 #
text
┌─────────────────────────────────────────────────────────────┐
│ 客户端保留事件 │
├─────────────────────────────────────────────────────────────┤
│ │
│ connect │
│ ───────────────────────────────────────────────────────── │
│ 连接成功时触发 │
│ socket.on('connect', () => { ... }); │
│ │
│ connect_error │
│ ───────────────────────────────────────────────────────── │
│ 连接错误时触发 │
│ socket.on('connect_error', (err) => { ... }); │
│ │
│ disconnect │
│ ───────────────────────────────────────────────────────── │
│ 连接断开时触发 │
│ socket.on('disconnect', (reason) => { ... }); │
│ │
│ reconnect │
│ ───────────────────────────────────────────────────────── │
│ 重连成功时触发 │
│ socket.on('reconnect', (attempt) => { ... }); │
│ │
│ reconnect_attempt │
│ ───────────────────────────────────────────────────────── │
│ 尝试重连时触发 │
│ socket.on('reconnect_attempt', (attempt) => { ... }); │
│ │
│ reconnect_error │
│ ───────────────────────────────────────────────────────── │
│ 重连错误时触发 │
│ socket.on('reconnect_error', (err) => { ... }); │
│ │
│ reconnect_failed │
│ ───────────────────────────────────────────────────────── │
│ 重连失败时触发 │
│ socket.on('reconnect_failed', () => { ... }); │
│ │
└─────────────────────────────────────────────────────────────┘
服务端保留事件 #
text
┌─────────────────────────────────────────────────────────────┐
│ 服务端保留事件 │
├─────────────────────────────────────────────────────────────┤
│ │
│ connection │
│ ───────────────────────────────────────────────────────── │
│ 新连接建立时触发 │
│ io.on('connection', (socket) => { ... }); │
│ │
│ disconnect │
│ ───────────────────────────────────────────────────────── │
│ 连接断开时触发 │
│ socket.on('disconnect', (reason) => { ... }); │
│ │
│ disconnecting │
│ ───────────────────────────────────────────────────────── │
│ 正在断开时触发(还在房间中) │
│ socket.on('disconnecting', (reason) => { ... }); │
│ │
└─────────────────────────────────────────────────────────────┘
使用示例 #
javascript
const socket = io();
socket.on('connect', () => {
console.log('连接成功:', socket.id);
});
socket.on('connect_error', (err) => {
console.log('连接错误:', err.message);
});
socket.on('disconnect', (reason) => {
console.log('断开连接:', reason);
if (reason === 'io server disconnect') {
socket.connect();
}
});
二进制数据 #
发送二进制数据 #
javascript
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setFloat64(0, 3.14);
socket.emit('binary', buffer);
socket.emit('binary-array', {
data: buffer,
name: 'test.bin'
});
const blob = new Blob(['Hello World'], { type: 'text/plain' });
socket.emit('blob', blob);
接收二进制数据 #
javascript
socket.on('binary', (buffer) => {
const view = new DataView(buffer);
const value = view.getFloat64(0);
console.log('收到二进制数据:', value);
});
socket.on('binary-array', (data) => {
console.log('名称:', data.name);
console.log('数据:', data.data);
});
socket.on('blob', (blob) => {
console.log('收到 Blob:', blob.size, blob.type);
});
文件传输示例 #
javascript
const fileInput = document.getElementById('file');
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = () => {
socket.emit('file', {
name: file.name,
type: file.type,
size: file.size,
data: reader.result
});
};
reader.readAsArrayBuffer(file);
});
javascript
socket.on('file', (file) => {
console.log('收到文件:', file.name, file.size);
const blob = new Blob([file.data], { type: file.type });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = file.name;
a.click();
});
错误处理 #
事件错误 #
javascript
socket.on('connect_error', (err) => {
console.log('连接错误:', err.message);
});
socket.on('error', (err) => {
console.log('错误:', err);
});
服务端发送错误 #
javascript
io.on('connection', (socket) => {
socket.on('login', (credentials, callback) => {
if (!credentials.username) {
return callback({ error: '用户名不能为空' });
}
if (!validateCredentials(credentials)) {
return callback({ error: '认证失败' });
}
callback({ success: true, user: { id: 123, name: credentials.username } });
});
});
客户端处理错误 #
javascript
socket.emit('login', { username: 'john', password: '123' }, (response) => {
if (response.error) {
console.log('登录失败:', response.error);
return;
}
console.log('登录成功:', response.user);
});
事件模式 #
请求-响应模式 #
javascript
socket.emit('get user', { id: 123 }, (response) => {
if (response.error) {
console.log('获取用户失败');
return;
}
console.log('用户信息:', response.user);
});
socket.on('get user', (query, callback) => {
getUserFromDB(query.id)
.then(user => callback({ user }))
.catch(err => callback({ error: err.message }));
});
发布-订阅模式 #
javascript
socket.emit('subscribe', { channel: 'news' });
socket.on('subscribed', (channel) => {
console.log(`已订阅: ${channel}`);
});
socket.on('news', (article) => {
console.log('新闻:', article);
});
socket.emit('unsubscribe', { channel: 'news' });
javascript
io.on('connection', (socket) => {
const subscriptions = new Set();
socket.on('subscribe', (data, callback) => {
socket.join(data.channel);
subscriptions.add(data.channel);
callback && callback({ success: true });
});
socket.on('unsubscribe', (data, callback) => {
socket.leave(data.channel);
subscriptions.delete(data.channel);
callback && callback({ success: true });
});
socket.on('disconnect', () => {
subscriptions.forEach(channel => {
socket.leave(channel);
});
});
});
流式数据 #
javascript
let count = 0;
const interval = setInterval(() => {
socket.emit('counter', count++);
if (count > 100) {
clearInterval(interval);
socket.emit('counter done');
}
}, 100);
socket.on('counter', (value) => {
console.log('计数:', value);
});
socket.on('counter done', () => {
console.log('计数完成');
});
事件设计最佳实践 #
命名规范 #
text
┌─────────────────────────────────────────────────────────────┐
│ 事件命名规范 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 推荐使用小写和连字符: │
│ ✅ chat-message │
│ ✅ user-joined │
│ ✅ room-created │
│ │
│ 或使用驼峰命名: │
│ ✅ chatMessage │
│ ✅ userJoined │
│ ✅ roomCreated │
│ │
│ 避免使用: │
│ ❌ ChatMessage │
│ ❌ chat_message │
│ ❌ CHAT_MESSAGE │
│ │
│ 语义清晰: │
│ ✅ user:join │
│ ✅ user:leave │
│ ✅ message:send │
│ │
└─────────────────────────────────────────────────────────────┘
数据结构设计 #
javascript
socket.emit('message', {
type: 'text',
content: 'Hello',
metadata: {
timestamp: Date.now(),
sender: 'user-123'
}
});
socket.emit('message', {
type: 'image',
content: {
url: 'https://example.com/image.jpg',
width: 800,
height: 600
},
metadata: {
timestamp: Date.now(),
sender: 'user-123'
}
});
TypeScript 类型定义 #
typescript
interface ClientToServerEvents {
'chat:message': (data: ChatMessage) => void;
'user:join': (data: { roomId: string }, callback: (success: boolean) => void) => void;
'user:leave': (roomId: string) => void;
}
interface ServerToClientEvents {
'chat:message': (data: ChatMessage) => void;
'user:joined': (data: { userId: string; username: string }) => void;
'user:left': (data: { userId: string }) => void;
'error': (error: { code: string; message: string }) => void;
}
interface ChatMessage {
id: string;
content: string;
sender: {
id: string;
name: string;
};
timestamp: number;
}
const io = new Server<ClientToServerEvents, ServerToClientEvents>();
io.on('connection', (socket) => {
socket.on('chat:message', (data) => {
io.emit('chat:message', data);
});
});
性能优化 #
批量发送 #
javascript
const messages = [];
function queueMessage(msg) {
messages.push(msg);
}
setInterval(() => {
if (messages.length > 0) {
socket.emit('batch messages', messages.splice(0));
}
}, 100);
节流发送 #
javascript
let lastEmit = 0;
const throttle = 100;
function emitPosition(x, y) {
const now = Date.now();
if (now - lastEmit >= throttle) {
socket.emit('position', { x, y });
lastEmit = now;
}
}
压缩数据 #
javascript
socket.emit('game state', {
p: [{ x: 100, y: 200 }, { x: 150, y: 250 }],
t: Date.now()
});
socket.on('game state', (data) => {
const players = data.p;
const timestamp = data.t;
});
下一步 #
最后更新:2026-03-29