Socket.IO服务端未处理客户端请求问题排查求助
我正在开发一个基于Node.js的应用,前端采用React,后端使用Express+Socket.IO架构并集成Passport认证。当前遇到的问题是:客户端发送Socket.IO事件后,后端未作出响应,请求似乎在发送后到后端处理的环节丢失。
后端运行在localhost:5000,客户端在localhost:3000。流程可正常推进至Organization.js中标记的代码行,但该行之后,事件虽已发送,socket.js中对应的路由却未被触发。前端无任何错误,socket.on('connect')显示连接已建立,但socket.js中的所有console.log均未触发,说明对应路由未命中。已开启Socket.IO调试,但日志未提供有效信息。
后端启动文件 - app.js
'use strict'; import express from 'express' import { createServer } from 'http' import path from 'path' import { fileURLToPath } from 'url'; import logger from 'morgan' import cookieParser from 'cookie-parser' import index from './routes/index.js' import session from 'express-session' import passport from './src/auth.js' import { Server } from 'socket.io' import { CrashCollector } from 'crashcollector' new CrashCollector('crash_logs', (err, exit) => { console.log('Cleaning up...') console.log('Saving some stuff...') // Calls callback funtion executing process.exit() // You can also just write process.exit() here exit() }) const app = express(); const server = createServer(app) const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const io = new Server(server, { cookie: { maxAge: 24 * 60 * 60 * 1000 }, cors: { origin: 'http://localhost:3000', credentials: true } }) const sessionMiddleware = session({ secret: "changeit", resave: false, saveUninitialized: false }); // uncomment after placing your favicon in /public //app.use(favicon(__dirname + '/public/favicon.ico')); app.use(sessionMiddleware) app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use(passport.initialize()); app.use(passport.session()); app.use('/', index); // catch 404 and forward to error handler app.use(function (req, res, next) { let err = new Error('Not Found'); err.status = 404; next(err); }); // error handlers // development error handler if (app.get('env') === 'development') { app.use(function (err, req, res, next) { console.log('dev error handler') res.status(err.status || 500); res.end() }); } // production error handler // no stacktraces leaked to user app.use(function (err, req, res, next) { console.log('prod error handler') res.status(err.status || 500); res.render('error', { message: err.message, error: {} }); }); app.set('port', process.env.PORT || 3000); //process.env.PORT = 5000 server.listen(app.get('port'), _ => { console.log(`Server listening on port ${app.get('port')}`) }) export { io, sessionMiddleware }
后端 - socket.js
import { io, sessionMiddleware } from '../app.js' import passport from '../src/auth.js' import { getSequel } from '../src/database.js' const wrap = middleware => (socket, next) => middleware(socket.request, {}, next); io.use((_, next) => { console.log('connection attempt') next() }) io.use(wrap(sessionMiddleware)); io.use(wrap(passport.initialize())); io.use(wrap(passport.session())); io.use((con, next) => { console.log('socket check', !!con.request.user) if (con.request.user) { next() } else { next(new Error('unauthorized')) } }); io.on('connection', (con) => { console.log(`new connection ${con.id}`); con.on('confirm', cb => { cb(con.request.user.name); }); con.on('eventTable', _ => { console.log('eventTable') con.emit(con.request?.user?.getEvents()?.map(e => e.toJSON())) }) });
客户端启动文件 - index.js
import React from 'react' import { createRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; import App from './App/App'; import { io } from 'socket.io-client' import configs from '../package.json' const container = document.getElementById('root'); const root = createRoot(container); export const socket = io.connect(configs.proxy, { //configs.proxy = http://localhost:5000 withCredentials: true }) socket.on('connect', _ => { console.log('socket connect', socket.id) }) socket.on('connect_error', _ => { console.log('socket connect_error') }) socket.on('disconnect', _ => { console.log('socket disconnect') }) socket.prependAny((eventName, ...args) => { console.log(eventName, args) }) root.render(( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode> ));
客户端 - App.js
import React from 'react' import { Route, Routes } from 'react-router-dom' import './App.css' import Login from './pages/Login' import Home from './pages/Home' import LoginCheck from './helper/LoginCheck' const App = () => { console.log('app') return ( <Routes> <Route exact path="/" element={<LoginCheck />} > <Route index element={<Home />} /> </Route> <Route path="/login" element={<Login />} /> </Routes> ) } export default App;
客户端 - Home.js
import React, { useState } from 'react'; import Org from './Organization' const Home = () => { console.log('home') return localStorage.getItem('isOrg') ? ( <Org /> ) : ( 'Employee' ) } export default Home;
客户端 - Organization.js
import React, { createElement, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom' import Countdown from 'ds-countdown' import { socket } from '../../index' const Org = () => { console.log('org') const setTable = (raffles) => { console.log('cb eventTable') JSON.parse(raffles).forEach(raffle => { const row = createElement('tr') const name = createElement('td') name.append(raffle.name) const num = createElement('td') num.append(raffle.num) const started = Date.parse(raffle.start) >= Date.now() const countdown = <><td> <p style={{ color: started ? 'red' : 'green' }}> {started ? 'Starts' : 'Ends'} in: <div id={`timer${raffle.id}`} /> </p> </td></> const action = createElement('td') const edit = createElement('button') edit.addEventListener('click', e => { //goto edit at raffle.id alert('edit') }) const end = createElement('button') end.addEventListener('click', e => { //set timer to 0 alert('start/end') }) action.append(edit) action.append(end) row.append(name) row.append(num) row.append(countdown) row.append(action) document.getElementById('eventTable').append(row) new Countdown({ id: `timer${raffle.id}`, targetTime: started ? raffle.end : raffle.start }) }) } useEffect(() => { console.log('useEffect') *socket.emit('eventTable')* // 问题发生在这里 socket.on('eventTable', setTable) return () => { socket.off('eventTable', setTable) } }) return (<> <h1 style={{ textAlign: 'center' }}> {localStorage.getItem('name')} </h1> <table style={{ marginLeft: 'auto', marginRight: 'auto' }} border="1"><caption> <h3>Scheduled Raffles</h3> </caption> <tbody id="eventTable"> <tr> <th>Name</th> <th>Num Participants</th> <th>Countdown</th> <th>Action</th> </tr> <tr> <td>Test</td> <td>10</td> <td>10:10:04</td> <td><button> Edit </button> <button> End Now </button> <button> Stop </button></td> </tr> </tbody> </table> </>); } export default Org;
Socket.IO调试日志
Server listening on port 5000 socket.io:server incoming connection with id 1wpATq-xiQ1jHttwAAAA +2s socket.io-parser decoded 0 as {"type":0,"nsp":"/"} +0ms socket.io:client connecting to namespace / +0ms socket.io:namespace adding socket to nsp / +0ms socket.io:socket socket connected - writing packet +0ms socket.io:socket join room f6lna-kbntjuwffpAAAB +3ms socket.io-parser encoding packet {"type":0,"data":{"sid":"f6lna-kbntjuwffpAAAB"},"nsp":"/"} +7ms socket.io-parser encoded {"type":0,"data":{"sid":"f6lna-kbntjuwffpAAAB"},"nsp":"/"} as 0{"sid":"f6lna-kbntjuwffpAAAB"} +1ms POST /login 200 653.887 ms - 37 socket.io-parser decoded 2["eventTable"] as {"type":2,"nsp":"/","data":["eventTable"]} +3s socket.io:socket got packet {"type":2,"nsp":"/","data":["eventTable"]} +3s socket.io:socket emitting event ["eventTable"] +0ms socket.io:socket dispatching an event ["eventTable"] +1ms socket.io-parser decoded 2["eventTable"] as {"type":2,"nsp":"/","data":["eventTable"]} +15ms socket.io:socket got packet {"type":2,"nsp":"/","data":["eventTable"]} +2ms socket.io:socket emitting event ["eventTable"] +0ms socket.io:socket dispatching an event ["eventTable"] +1ms
问题排查与修复步骤
确认socket.js被正确加载:从调试日志可见后端已收到
eventTable事件,但socket.js中的日志未触发,说明该文件未被导入到应用入口。需在app.js末尾添加导入语句,例如import './src/socket.js'(根据实际路径调整)。修正后端事件发射格式:socket.js中
con.emit(con.request?.user?.getEvents()?.map(e => e.toJSON()))缺少事件名称参数,正确写法应为:con.emit('eventTable', con.request?.user?.getEvents()?.map(e => e.toJSON()))优化useEffect依赖项:Organization.js中的useEffect未设置依赖数组,会导致组件重复渲染时多次发送事件。添加空依赖数组确保仅在组件挂载时发送一次:
useEffect(() => { console.log('useEffect') socket.emit('eventTable') socket.on('eventTable', setTable) return () => { socket.off('eventTable', setTable) } }, [])排查认证中间件阻塞:暂时注释socket.js中的Passport认证检查逻辑,测试事件是否能正常触发,确认是否为认证环节导致连接未完成。
内容的提问来源于stack exchange,提问作者Ethan Parsons

