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
相关产品推荐
相关产品推荐

