You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js中如何在Controller中正确导出并使用Socket.IO?

通用可复用的Socket.IO封装与控制器调用方案

问题根源

核心问题是控制器中拿到的io实例未正确关联已建立的客户端连接,或是在连接建立前就执行了事件绑定/消息发送操作,导致无法触达客户端。只有在connection回调内操作有效,是因为此时能直接获取当前连接的socket实例,且确保客户端已完成连接。

解决方案:封装全局可访问的Socket.IO实例与连接管理

1. 重构RealTime类,暴露全局实例与连接池

// realtime.js
const { Server } = require('socket.io');

class RealTime {
  constructor() {
    this.io = null;
    // 维护在线客户端的socket连接池
    this.connectedSockets = new Map();
  }

  init(server) {
    this.io = new Server(server, {
      cors: {
        origin: "你的Angular前端域名", // 例:http://localhost:4200
        methods: ["GET", "POST"]
      }
    });

    this.io.on('connection', (socket) => {
      console.log(`客户端 ${socket.id} 已连接`);
      this.connectedSockets.set(socket.id, socket);

      socket.on('disconnect', () => {
        console.log(`客户端 ${socket.id} 已断开`);
        this.connectedSockets.delete(socket.id);
      });
    });

    return this.io;
  }

  getIO() {
    if (!this.io) {
      throw new Error('Socket.IO 尚未初始化,请先调用 init 方法');
    }
    return this.io;
  }

  getConnectedSockets() {
    return this.connectedSockets;
  }
}

// 导出单例,确保全局仅一个实例
module.exports = new RealTime();

2. 在index.js中初始化并挂载到Express

// index.js
const express = require('express');
const http = require('http');
const realTime = require('./realtime');

const app = express();
const server = http.createServer(app);

// 初始化Socket.IO并关联HTTP服务器
const io = realTime.init(server);

// 将io挂载到Express实例,方便控制器访问
app.set('io', io);

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log(`服务器运行在端口 ${PORT}`);
});

3. 在customerController中正确调用Socket.IO

方式一:通过Express实例获取io

// customerController.js
exports.sendCustomEvent = (req, res) => {
  const io = req.app.get('io');
  if (!io) {
    return res.status(500).json({ error: 'Socket.IO 未初始化' });
  }

  // 给所有客户端发送消息
  io.emit('custom-emit', { message: '来自客户控制器的消息' });

  // 给指定客户端发送(需提前获取目标socket.id)
  // const targetSocket = require('./realtime').getConnectedSockets().get('目标socketId');
  // if (targetSocket) targetSocket.emit('custom-emit', { message: '专属消息' });

  res.status(200).json({ success: true });
};

// 绑定客户端事件(建议在项目启动时统一执行)
exports.bindCustomEvent = () => {
  const io = require('./realtime').getIO();
  io.on('connection', (socket) => {
    socket.on('custom-event', (data) => {
      console.log('收到custom-event:', data);
      // 业务逻辑:如存储数据、广播响应
      socket.emit('custom-event-response', { status: 'success', data });
    });
  });
};

方式二:直接导入RealTime单例

// customerController.js
const realTime = require('./realtime');

exports.sendCustomEvent = (req, res) => {
  try {
    const io = realTime.getIO();
    io.emit('custom-emit', { message: '来自客户控制器的消息' });
    res.status(200).json({ success: true });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
};

4. Angular前端Socket.IO客户端配置

// socket.service.ts
import { Injectable } from '@angular/core';
import { io, Socket } from 'socket.io-client';
import { Observable } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class SocketService {
  private socket: Socket;

  constructor() {
    this.socket = io('http://localhost:3000', {
      transports: ['websocket'] // 强制使用WebSocket,避免轮询问题
    });

    this.socket.on('connect', () => {
      console.log('已连接到Socket.IO服务器');
    });

    this.socket.on('disconnect', () => {
      console.log('与Socket.IO服务器断开连接');
    });
  }

  sendCustomEvent(data: any) {
    this.socket.emit('custom-event', data);
  }

  listenToCustomEmit(): Observable<any> {
    return new Observable(observer => {
      this.socket.on('custom-emit', data => observer.next(data));
    });
  }

  listenToCustomEventResponse(): Observable<any> {
    return new Observable(observer => {
      this.socket.on('custom-event-response', data => observer.next(data));
    });
  }
}

关键注意事项

  • 单例模式:必须保证RealTime类是单例,避免创建多个Socket.IO实例导致连接混乱。
  • 连接时机:控制器发送消息前,需确认io实例已初始化,且客户端已建立连接(可通过连接池判断在线状态)。
  • 事件绑定规范:全局事件建议在RealTime的init方法或项目启动时统一绑定,避免重复绑定引发异常。
  • CORS配置:后端必须正确配置CORS允许前端域名,否则客户端无法建立连接。

内容的提问来源于stack exchange,提问作者Atique Ahmed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 04:45:43