Socket.io 中文教程

1. 核心概念

Socket.io 是一个功能强大的实时通信库,由 Guillermo Rauch 开发和维护。它提供了一个简单而灵活的 API,用于在客户端和服务器之间建立实时双向通信。

1.1 什么是 Socket.io?

Socket.io 是一个实时通信库,提供了:

  • 基于 WebSocket 的实时双向通信
  • 自动降级到轮询(Polling)等其他传输方式
  • 房间(Rooms)和命名空间(Namespaces)支持
  • 事件系统
  • 错误处理和重连机制
  • 与各种前端框架的集成

1.2 为什么选择 Socket.io?

  • 实时双向通信:提供了真正的实时双向通信能力
  • 可靠性:自动处理连接断开和重连
  • 兼容性:支持所有现代浏览器,自动降级到其他传输方式
  • 扩展性:支持房间和命名空间,便于组织和管理连接
  • 易用性:简洁的 API 设计,易于上手

2. 安装与配置

2.1 基本安装

在 Node.js 项目中安装 Socket.io:

npm install socket.io

2.2 基本配置

创建一个简单的 Socket.io 服务器:

import { createServer } from 'node:http';
import { Server } from 'socket.io';

const httpServer = createServer();
const io = new Server(httpServer, {
  // 配置选项
  cors: {
    origin: '*',
    methods: ['GET', 'POST']
  }
});

// 监听连接事件
io.on('connection', (socket) => {
  console.log('新用户连接:', socket.id);
  
  // 监听消息事件
  socket.on('message', (data) => {
    console.log('收到消息:', data);
    // 广播消息给所有客户端
    io.emit('message', data);
  });
  
  // 监听断开连接事件
  socket.on('disconnect', () => {
    console.log('用户断开连接:', socket.id);
  });
});

// 启动服务器
httpServer.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000');
});

2.3 客户端安装

在前端项目中安装 Socket.io 客户端:

npm install socket.io-client

2.4 客户端配置

创建一个简单的 Socket.io 客户端:

import { io } from 'socket.io-client';

// 连接到服务器
const socket = io('http://localhost:3000', {
  // 配置选项
  reconnection: true,
  reconnectionAttempts: 5,
  reconnectionDelay: 1000
});

// 监听连接事件
socket.on('connect', () => {
  console.log('连接成功:', socket.id);
});

// 监听消息事件
socket.on('message', (data) => {
  console.log('收到消息:', data);
  // 处理消息
});

// 监听断开连接事件
socket.on('disconnect', () => {
  console.log('断开连接');
});

// 发送消息
function sendMessage(message) {
  socket.emit('message', message);
}

3. 基本使用

3.1 事件系统

Socket.io 使用事件系统进行通信,服务器和客户端都可以触发和监听事件:

3.1.1 服务器端事件

// 监听连接事件
io.on('connection', (socket) => {
  // 监听自定义事件
  socket.on('chat message', (msg) => {
    console.log('收到聊天消息:', msg);
    // 广播消息给所有客户端
    io.emit('chat message', msg);
  });
  
  // 监听用户加入事件
  socket.on('user join', (username) => {
    console.log('用户加入:', username);
    // 广播用户加入消息
    io.emit('user joined', username);
  });
});

3.1.2 客户端事件

// 连接到服务器
const socket = io('http://localhost:3000');

// 发送聊天消息
socket.emit('chat message', 'Hello everyone!');

// 发送用户加入事件
socket.emit('user join', 'John Doe');

// 监听聊天消息事件
socket.on('chat message', (msg) => {
  console.log('收到聊天消息:', msg);
  // 显示消息
});

// 监听用户加入事件
socket.on('user joined', (username) => {
  console.log('用户加入:', username);
  // 显示用户加入通知
});

3.2 房间(Rooms)

Socket.io 的房间功能允许你将客户端分组,以便向特定组的客户端发送消息:

3.2.1 服务器端房间操作

io.on('connection', (socket) => {
  // 加入房间
  socket.on('join room', (roomId) => {
    socket.join(roomId);
    console.log(`用户 ${socket.id} 加入房间 ${roomId}`);
    // 向房间内的所有客户端发送消息(除了当前客户端)
    socket.to(roomId).emit('user joined', `用户 ${socket.id} 加入了房间`);
  });
  
  // 离开房间
  socket.on('leave room', (roomId) => {
    socket.leave(roomId);
    console.log(`用户 ${socket.id} 离开房间 ${roomId}`);
    // 向房间内的所有客户端发送消息
    socket.to(roomId).emit('user left', `用户 ${socket.id} 离开了房间`);
  });
  
  // 向特定房间发送消息
  socket.on('send to room', ({ roomId, message }) => {
    io.to(roomId).emit('room message', message);
  });
});

3.2.2 客户端房间操作

// 连接到服务器
const socket = io('http://localhost:3000');

// 加入房间
socket.emit('join room', 'room1');

// 离开房间
socket.emit('leave room', 'room1');

// 向房间发送消息
socket.emit('send to room', {
  roomId: 'room1',
  message: 'Hello room1!'
});

// 监听房间消息
socket.on('room message', (message) => {
  console.log('收到房间消息:', message);
  // 显示消息
});

// 监听用户加入/离开事件
socket.on('user joined', (notification) => {
  console.log(notification);
  // 显示通知
});

socket.on('user left', (notification) => {
  console.log(notification);
  // 显示通知
});

3.3 命名空间(Namespaces)

Socket.io 的命名空间功能允许你创建多个独立的通信通道:

3.3.1 服务器端命名空间

// 创建默认命名空间
const io = new Server(httpServer);

// 创建自定义命名空间
const chatNamespace = io.of('/chat');
const gameNamespace = io.of('/game');

// 默认命名空间的连接处理
io.on('connection', (socket) => {
  console.log('用户连接到默认命名空间:', socket.id);
});

// 聊天命名空间的连接处理
chatNamespace.on('connection', (socket) => {
  console.log('用户连接到聊天命名空间:', socket.id);
  
  // 聊天命名空间的事件处理
  socket.on('chat message', (msg) => {
    chatNamespace.emit('chat message', msg);
  });
});

// 游戏命名空间的连接处理
gameNamespace.on('connection', (socket) => {
  console.log('用户连接到游戏命名空间:', socket.id);
  
  // 游戏命名空间的事件处理
  socket.on('game move', (move) => {
    gameNamespace.emit('game move', move);
  });
});

3.3.2 客户端命名空间

// 连接到默认命名空间
const defaultSocket = io('http://localhost:3000');

// 连接到聊天命名空间
const chatSocket = io('http://localhost:3000/chat');

// 连接到游戏命名空间
const gameSocket = io('http://localhost:3000/game');

// 使用聊天命名空间
tchatSocket.emit('chat message', 'Hello chat!');

chatSocket.on('chat message', (msg) => {
  console.log('收到聊天消息:', msg);
});

// 使用游戏命名空间
gameSocket.emit('game move', { player: 'John', move: 'left' });

gameSocket.on('game move', (move) => {
  console.log('收到游戏移动:', move);
});

4. 高级特性

4.1 中间件

Socket.io 支持中间件,用于处理连接和消息:

4.1.1 服务器端中间件

import { Server } from 'socket.io';

const io = new Server(httpServer);

// 连接中间件
io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  
  // 验证 token
  if (!token) {
    return next(new Error('未提供认证令牌'));
  }
  
  // 验证 token 有效性
  // 这里只是示例,实际应用中需要实现真实的验证逻辑
  if (token !== 'valid-token') {
    return next(new Error('无效的认证令牌'));
  }
  
  // 将用户信息附加到 socket 对象
  socket.user = { id: '1', name: 'John Doe' };
  next();
});

// 消息中间件
io.on('connection', (socket) => {
  console.log('用户连接:', socket.user.name);
  
  // 可以在这里处理消息
});

4.2 错误处理

Socket.io 提供了错误处理机制:

4.2.1 服务器端错误处理

io.on('connection', (socket) => {
  // 监听错误事件
  socket.on('error', (error) => {
    console.error('Socket 错误:', error);
  });
  
  // 处理可能出错的操作
  socket.on('risky operation', (data, callback) => {
    try {
      // 执行操作
      const result = performRiskyOperation(data);
      callback({ success: true, result });
    } catch (error) {
      console.error('操作错误:', error);
      callback({ success: false, error: error.message });
    }
  });
});

function performRiskyOperation(data) {
  // 模拟可能出错的操作
  if (!data) {
    throw new Error('数据不能为空');
  }
  return '操作成功';
}

4.2.2 客户端错误处理

const socket = io('http://localhost:3000');

// 监听连接错误
socket.on('connect_error', (error) => {
  console.error('连接错误:', error);
});

// 监听错误事件
socket.on('error', (error) => {
  console.error('Socket 错误:', error);
});

// 发送可能出错的操作
socket.emit('risky operation', { data: 'test' }, (response) => {
  if (response.success) {
    console.log('操作成功:', response.result);
  } else {
    console.error('操作失败:', response.error);
  }
});

4.3 重连机制

Socket.io 内置了重连机制:

4.3.1 客户端重连配置

const socket = io('http://localhost:3000', {
  // 重连配置
  reconnection: true, // 启用重连
  reconnectionAttempts: 5, // 重连尝试次数
  reconnectionDelay: 1000, // 重连延迟(毫秒)
  reconnectionDelayMax: 5000, // 最大重连延迟(毫秒)
  timeout: 20000 // 连接超时(毫秒)
});

// 监听重连尝试事件
socket.on('reconnect_attempt', (attemptNumber) => {
  console.log(`重连尝试 ${attemptNumber}`);
});

// 监听重连成功事件
socket.on('reconnect', (attemptNumber) => {
  console.log(`重连成功,尝试次数: ${attemptNumber}`);
});

// 监听重连失败事件
socket.on('reconnect_failed', () => {
  console.log('重连失败');
});

4.4 状态管理

在 Socket.io 应用中管理状态:

4.4.1 服务器端状态管理

import { Server } from 'socket.io';

const io = new Server(httpServer);

// 存储在线用户
const onlineUsers = new Map();

io.on('connection', (socket) => {
  // 添加用户到在线列表
  onlineUsers.set(socket.id, {
    id: socket.id,
    name: socket.handshake.auth.username || `用户${socket.id.slice(-4)}`
  });
  
  // 广播在线用户列表
  io.emit('online users', Array.from(onlineUsers.values()));
  
  // 监听断开连接事件
  socket.on('disconnect', () => {
    // 从在线列表中移除用户
    onlineUsers.delete(socket.id);
    // 广播在线用户列表
    io.emit('online users', Array.from(onlineUsers.values()));
  });
});

4.4.2 客户端状态管理

const socket = io('http://localhost:3000', {
  auth: {
    username: 'John Doe'
  }
});

// 存储在线用户
let onlineUsers = [];

// 监听在线用户列表事件
socket.on('online users', (users) => {
  onlineUsers = users;
  console.log('在线用户:', onlineUsers);
  // 更新 UI
  updateOnlineUsersList(users);
});

function updateOnlineUsersList(users) {
  // 更新 UI 显示在线用户列表
  console.log('更新在线用户列表:', users);
}

5. 最佳实践

5.1 项目结构

推荐的 Socket.io 项目结构:

├── src/
│   ├── server/             # 服务器端代码
│   │   ├── index.js        # 服务器入口
│   │   ├── socket.js       # Socket.io 配置
│   │   ├── middleware/     # 中间件
│   │   ├── handlers/       # 事件处理器
│   │   ├── rooms/          # 房间管理
│   │   └── utils/          # 工具函数
│   ├── client/             # 客户端代码
│   │   ├── index.js        # 客户端入口
│   │   ├── socket.js       # Socket.io 客户端配置
│   │   ├── components/     # 组件
│   │   └── utils/          # 工具函数
│   └── shared/             # 共享代码
│       └── events.js       # 事件名称
├── package.json
└── .env

5.2 性能优化

  • 使用房间:将客户端分组,只向相关客户端发送消息
  • 限制消息大小:避免发送过大的消息
  • 批量发送消息:将多个小消息合并为一个大消息发送
  • 使用二进制数据:对于大型数据,使用二进制格式
  • 监控连接数:监控并限制同时连接的客户端数量

5.3 安全性

  • 认证和授权:实现用户认证和权限控制
  • 输入验证:验证所有客户端输入
  • 速率限制:防止消息轰炸
  • 加密通信:在生产环境中使用 HTTPS
  • CORS 配置:正确配置 CORS 选项

6. 实用案例

6.1 构建实时聊天应用

6.1.1 服务器端实现

import { createServer } from 'node:http';
import { Server } from 'socket.io';

const httpServer = createServer();
const io = new Server(httpServer, {
  cors: {
    origin: '*',
    methods: ['GET', 'POST']
  }
});

// 存储在线用户
const onlineUsers = new Map();

io.on('connection', (socket) => {
  // 添加用户到在线列表
  const username = socket.handshake.auth.username || `用户${socket.id.slice(-4)}`;
  onlineUsers.set(socket.id, {
    id: socket.id,
    name: username
  });
  
  console.log(`${username} 连接了`);
  
  // 广播用户加入消息
  io.emit('user joined', {
    id: socket.id,
    name: username
  });
  
  // 广播在线用户列表
  io.emit('online users', Array.from(onlineUsers.values()));
  
  // 监听聊天消息事件
  socket.on('chat message', (message) => {
    console.log('收到消息:', message);
    // 广播消息
    io.emit('chat message', {
      id: socket.id,
      name: username,
      message: message,
      timestamp: new Date().toISOString()
    });
  });
  
  // 监听断开连接事件
  socket.on('disconnect', () => {
    console.log(`${username} 断开了连接`);
    // 从在线列表中移除用户
    onlineUsers.delete(socket.id);
    // 广播用户离开消息
    io.emit('user left', {
      id: socket.id,
      name: username
    });
    // 广播在线用户列表
    io.emit('online users', Array.from(onlineUsers.values()));
  });
});

httpServer.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000');
});

6.1.2 客户端实现

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>实时聊天应用</title>
  <style>
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    
    body {
      font-family: Arial, sans-serif;
      line-height: 1.6;
      background-color: #f4f4f4;
    }
    
    .container {
      max-width: 800px;
      margin: 20px auto;
      background-color: #fff;
      border-radius: 8px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
      overflow: hidden;
    }
    
    .header {
      background-color: #333;
      color: #fff;
      padding: 15px;
      text-align: center;
    }
    
    .chat-container {
      display: flex;
      height: 500px;
    }
    
    .users-list {
      width: 200px;
      background-color: #f9f9f9;
      border-right: 1px solid #ddd;
      padding: 15px;
      overflow-y: auto;
    }
    
    .users-list h3 {
      margin-bottom: 10px;
      color: #333;
    }
    
    .users-list ul {
      list-style: none;
    }
    
    .users-list li {
      padding: 8px;
      margin-bottom: 5px;
      background-color: #e8f4f8;
      border-radius: 4px;
    }
    
    .messages {
      flex: 1;
      padding: 15px;
      overflow-y: auto;
    }
    
    .message {
      margin-bottom: 15px;
      padding: 10px;
      border-radius: 8px;
      max-width: 70%;
    }
    
    .message.own {
      background-color: #e3f2fd;
      align-self: flex-end;
      margin-left: auto;
    }
    
    .message.other {
      background-color: #f1f1f1;
      align-self: flex-start;
    }
    
    .message-info {
      font-size: 12px;
      color: #666;
      margin-bottom: 5px;
    }
    
    .message-text {
      font-size: 14px;
    }
    
    .input-area {
      display: flex;
      padding: 15px;
      border-top: 1px solid #ddd;
    }
    
    .input-area input {
      flex: 1;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 4px;
      margin-right: 10px;
    }
    
    .input-area button {
      padding: 10px 20px;
      background-color: #333;
      color: #fff;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    
    .input-area button:hover {
      background-color: #555;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <h1>实时聊天应用</h1>
    </div>
    <div class="chat-container">
      <div class="users-list">
        <h3>在线用户</h3>
        <ul id="users-list"></ul>
      </div>
      <div class="messages" id="messages"></div>
    </div>
    <div class="input-area">
      <input type="text" id="message-input" placeholder="输入消息...">
      <button id="send-button">发送</button>
    </div>
  </div>
  
  <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
  <script>
    // 连接到服务器
    const socket = io('http://localhost:3000', {
      auth: {
        username: prompt('请输入您的用户名:')
      }
    });
    
    const messagesContainer = document.getElementById('messages');
    const usersList = document.getElementById('users-list');
    const messageInput = document.getElementById('message-input');
    const sendButton = document.getElementById('send-button');
    
    // 发送消息
    function sendMessage() {
      const message = messageInput.value.trim();
      if (message) {
        socket.emit('chat message', message);
        messageInput.value = '';
      }
    }
    
    // 监听发送按钮点击事件
    sendButton.addEventListener('click', sendMessage);
    
    // 监听回车键事件
    messageInput.addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        sendMessage();
      }
    });
    
    // 显示消息
    function displayMessage(message) {
      const messageElement = document.createElement('div');
      messageElement.className = `message ${message.id === socket.id ? 'own' : 'other'}`;
      
      const messageInfo = document.createElement('div');
      messageInfo.className = 'message-info';
      messageInfo.textContent = `${message.name} · ${new Date(message.timestamp).toLocaleTimeString()}`;
      
      const messageText = document.createElement('div');
      messageText.className = 'message-text';
      messageText.textContent = message.message;
      
      messageElement.appendChild(messageInfo);
      messageElement.appendChild(messageText);
      messagesContainer.appendChild(messageElement);
      
      // 滚动到底部
      messagesContainer.scrollTop = messagesContainer.scrollHeight;
    }
    
    // 更新在线用户列表
    function updateUsersList(users) {
      usersList.innerHTML = '';
      users.forEach(user => {
        const userElement = document.createElement('li');
        userElement.textContent = user.name;
        usersList.appendChild(userElement);
      });
    }
    
    // 显示系统消息
    function displaySystemMessage(message) {
      const messageElement = document.createElement('div');
      messageElement.style.cssText = `
        text-align: center;
        margin: 10px 0;
        color: #666;
        font-size: 12px;
        font-style: italic;
      `;
      messageElement.textContent = message;
      messagesContainer.appendChild(messageElement);
      
      // 滚动到底部
      messagesContainer.scrollTop = messagesContainer.scrollHeight;
    }
    
    // 监听聊天消息事件
    socket.on('chat message', displayMessage);
    
    // 监听用户加入事件
    socket.on('user joined', (user) => {
      displaySystemMessage(`${user.name} 加入了聊天`);
    });
    
    // 监听用户离开事件
    socket.on('user left', (user) => {
      displaySystemMessage(`${user.name} 离开了聊天`);
    });
    
    // 监听在线用户列表事件
    socket.on('online users', updateUsersList);
    
    // 监听连接错误事件
    socket.on('connect_error', (error) => {
      console.error('连接错误:', error);
      displaySystemMessage('连接服务器失败,请刷新页面重试');
    });
  </script>
</body>
</html>

6.2 构建多人在线游戏

6.2.1 服务器端实现

import { createServer } from 'node:http';
import { Server } from 'socket.io';

const httpServer = createServer();
const io = new Server(httpServer, {
  cors: {
    origin: '*',
    methods: ['GET', 'POST']
  }
});

// 游戏状态
const gameState = {
  players: new Map(),
  objects: []
};

io.on('connection', (socket) => {
  console.log('玩家连接:', socket.id);
  
  // 创建玩家
  const player = {
    id: socket.id,
    x: Math.random() * 400 + 50,
    y: Math.random() * 400 + 50,
    color: `hsl(${Math.random() * 360}, 70%, 50%)`,
    score: 0
  };
  
  // 添加玩家到游戏状态
  gameState.players.set(socket.id, player);
  
  // 发送初始游戏状态给新玩家
  socket.emit('game state', {
    players: Array.from(gameState.players.values()),
    objects: gameState.objects
  });
  
  // 广播新玩家加入
  socket.broadcast.emit('player joined', player);
  
  // 监听玩家移动事件
  socket.on('player move', (move) => {
    const player = gameState.players.get(socket.id);
    if (player) {
      // 更新玩家位置
      if (move.x !== undefined) player.x = move.x;
      if (move.y !== undefined) player.y = move.y;
      
      // 广播玩家移动
      socket.broadcast.emit('player moved', {
        id: socket.id,
        x: player.x,
        y: player.y
      });
    }
  });
  
  // 监听玩家得分事件
  socket.on('player score', () => {
    const player = gameState.players.get(socket.id);
    if (player) {
      // 增加玩家分数
      player.score += 1;
      
      // 广播玩家得分
      io.emit('player scored', {
        id: socket.id,
        score: player.score
      });
    }
  });
  
  // 监听断开连接事件
  socket.on('disconnect', () => {
    console.log('玩家断开连接:', socket.id);
    // 从游戏状态中移除玩家
    gameState.players.delete(socket.id);
    // 广播玩家离开
    socket.broadcast.emit('player left', socket.id);
  });
});

httpServer.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000');
});

6.2.2 客户端实现

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>多人在线游戏</title>
  <style>
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    
    body {
      font-family: Arial, sans-serif;
      background-color: #f0f0f0;
    }
    
    .game-container {
      max-width: 800px;
      margin: 20px auto;
      background-color: #fff;
      border-radius: 8px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
      overflow: hidden;
    }
    
    .game-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 15px;
      background-color: #333;
      color: #fff;
    }
    
    .score {
      font-size: 18px;
      font-weight: bold;
    }
    
    .game-area {
      position: relative;
      width: 100%;
      height: 500px;
      background-color: #e8f4f8;
      overflow: hidden;
    }
    
    .player {
      position: absolute;
      width: 30px;
      height: 30px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #fff;
      font-size: 12px;
      font-weight: bold;
      transition: all 0.1s ease;
    }
    
    .object {
      position: absolute;
      width: 20px;
      height: 20px;
      background-color: #ff6b6b;
      border-radius: 50%;
    }
    
    .game-instructions {
      padding: 15px;
      background-color: #f9f9f9;
      border-top: 1px solid #ddd;
    }
    
    .game-instructions h3 {
      margin-bottom: 10px;
      color: #333;
    }
    
    .game-instructions p {
      margin-bottom: 5px;
      font-size: 14px;
      color: #666;
    }
  </style>
</head>
<body>
  <div class="game-container">
    <div class="game-header">
      <h1>多人在线游戏</h1>
      <div class="score">得分: <span id="score">0</span></div>
    </div>
    <div class="game-area" id="game-area"></div>
    <div class="game-instructions">
      <h3>游戏说明</h3>
      <p>• 使用鼠标移动你的角色</p>
      <p>• 收集红色圆形物体获得分数</p>
      <p>• 查看其他玩家的位置和分数</p>
    </div>
  </div>
  
  <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
  <script>
    // 连接到服务器
    const socket = io('http://localhost:3000');
    
    const gameArea = document.getElementById('game-area');
    const scoreElement = document.getElementById('score');
    
    // 游戏状态
    const gameState = {
      players: new Map(),
      objects: [],
      myId: null,
      score: 0
    };
    
    // 创建玩家元素
    function createPlayerElement(player) {
      const element = document.createElement('div');
      element.id = `player-${player.id}`;
      element.className = 'player';
      element.style.left = `${player.x}px`;
      element.style.top = `${player.y}px`;
      element.style.backgroundColor = player.color;
      element.textContent = player.id.slice(-2);
      gameArea.appendChild(element);
    }
    
    // 更新玩家位置
    function updatePlayerPosition(id, x, y) {
      const element = document.getElementById(`player-${id}`);
      if (element) {
        element.style.left = `${x}px`;
        element.style.top = `${y}px`;
      }
    }
    
    // 移除玩家元素
    function removePlayerElement(id) {
      const element = document.getElementById(`player-${id}`);
      if (element) {
        element.remove();
      }
    }
    
    // 创建物体元素
    function createObjectElement(object, index) {
      const element = document.createElement('div');
      element.id = `object-${index}`;
      element.className = 'object';
      element.style.left = `${object.x}px`;
      element.style.top = `${object.y}px`;
      gameArea.appendChild(element);
    }
    
    // 移除物体元素
    function removeObjectElement(index) {
      const element = document.getElementById(`object-${index}`);
      if (element) {
        element.remove();
      }
    }
    
    // 生成随机物体
    function generateObjects() {
      gameState.objects = [];
      
      // 清空现有物体
      document.querySelectorAll('.object').forEach(el => el.remove());
      
      // 生成 10 个随机物体
      for (let i = 0; i < 10; i++) {
        const object = {
          x: Math.random() * (gameArea.clientWidth - 20) + 10,
          y: Math.random() * (gameArea.clientHeight - 20) + 10
        };
        gameState.objects.push(object);
        createObjectElement(object, i);
      }
    }
    
    // 检查碰撞
    function checkCollisions() {
      const myPlayer = gameState.players.get(gameState.myId);
      if (!myPlayer) return;
      
      for (let i = 0; i < gameState.objects.length; i++) {
        const object = gameState.objects[i];
        const distance = Math.sqrt(
          Math.pow(myPlayer.x + 15 - (object.x + 10), 2) +
          Math.pow(myPlayer.y + 15 - (object.y + 10), 2)
        );
        
        if (distance < 25) {
          // 碰撞检测
          gameState.objects.splice(i, 1);
          removeObjectElement(i);
          gameState.score += 1;
          scoreElement.textContent = gameState.score;
          
          // 通知服务器得分
          socket.emit('player score');
          break;
        }
      }
    }
    
    // 监听鼠标移动
    gameArea.addEventListener('mousemove', (e) => {
      const rect = gameArea.getBoundingClientRect();
      const x = e.clientX - rect.left - 15;
      const y = e.clientY - rect.top - 15;
      
      // 限制玩家在游戏区域内
      const clampedX = Math.max(0, Math.min(x, gameArea.clientWidth - 30));
      const clampedY = Math.max(0, Math.min(y, gameArea.clientHeight - 30));
      
      // 更新本地玩家位置
      const myPlayer = gameState.players.get(gameState.myId);
      if (myPlayer) {
        myPlayer.x = clampedX;
        myPlayer.y = clampedY;
        updatePlayerPosition(gameState.myId, clampedX, clampedY);
        
        // 发送移动信息到服务器
        socket.emit('player move', { x: clampedX, y: clampedY });
        
        // 检查碰撞
        checkCollisions();
      }
    });
    
    // 监听游戏状态事件
    socket.on('game state', (state) => {
      // 更新玩家
      state.players.forEach(player => {
        gameState.players.set(player.id, player);
        if (!document.getElementById(`player-${player.id}`)) {
          createPlayerElement(player);
        }
      });
      
      // 更新物体
      gameState.objects = state.objects;
      document.querySelectorAll('.object').forEach(el => el.remove());
      state.objects.forEach((object, index) => {
        createObjectElement(object, index);
      });
      
      // 设置我的 ID
      if (!gameState.myId) {
        gameState.myId = socket.id;
      }
    });
    
    // 监听玩家加入事件
    socket.on('player joined', (player) => {
      gameState.players.set(player.id, player);
      createPlayerElement(player);
    });
    
    // 监听玩家移动事件
    socket.on('player moved', (data) => {
      const player = gameState.players.get(data.id);
      if (player) {
        player.x = data.x;
        player.y = data.y;
        updatePlayerPosition(data.id, data.x, data.y);
      }
    });
    
    // 监听玩家离开事件
    socket.on('player left', (id) => {
      gameState.players.delete(id);
      removePlayerElement(id);
    });
    
    // 监听玩家得分事件
    socket.on('player scored', (data) => {
      const player = gameState.players.get(data.id);
      if (player) {
        player.score = data.score;
        
        // 如果是自己得分,更新分数显示
        if (data.id === gameState.myId) {
          gameState.score = data.score;
          scoreElement.textContent = data.score;
        }
      }
    });
    
    // 初始化游戏
    function initGame() {
      generateObjects();
    }
    
    // 启动游戏
    initGame();
  </script>
</body>
</html>

7. 总结

Socket.io 是一个功能强大、易用的实时通信库,为开发者提供了构建实时应用的完整解决方案。通过本教程的学习,你应该已经掌握了:

  • Socket.io 的核心概念和基本原理
  • 如何安装和配置 Socket.io
  • 如何使用 Socket.io 的基本功能,如事件系统、房间和命名空间
  • 如何使用 Socket.io 的高级特性,如中间件、错误处理和重连机制
  • 最佳实践和实用案例

Socket.io 适用于各种实时应用场景,如聊天应用、多人游戏、实时协作工具、实时监控系统等。它提供了可靠的实时通信能力,同时具有良好的兼容性和扩展性。

无论是构建小型应用还是大型企业系统,Socket.io 都能满足你的实时通信需求。

8. 参考资料

« 上一篇 Fastify GQL 中文教程 下一篇 » SockJS 中文教程