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

下一步 #

现在你已经掌握了 Socket.IO 的事件系统,接下来学习 房间与命名空间,了解如何实现群组通信和逻辑隔离!

最后更新:2026-03-29