React+Node+Socket.IO聊天应用卡死,报connection failed:insufficient resources错误
错误原因与解决方案
错误根源
- 重复创建Socket连接:Chat组件每次重新渲染时,组件顶层的
socket = io(ENDPOINT)都会执行一次创建新连接,同时第一个useEffect依赖name和room,每次状态更新又会再创建一次连接,旧连接未主动关闭,短时间内产生大量TCP连接耗尽设备网络资源,触发资源不足报错。 - 事件监听泄漏:第二个useEffect依赖joined数组,每次joined更新都会新增一个
userName事件监听,旧监听未被移除,导致回调重复执行、内存占用持续升高,进一步加剧资源消耗。 - 无连接释放逻辑:组件卸载时没有主动断开Socket连接,连接持续占用资源得不到释放。
修复方案
直接替换原有Chat组件的逻辑即可:
import React from "react"; import SendIcon from "@material-ui/icons/Send"; import "./chat.css"; import { useEffect, useState } from "react"; import { useLocation } from "react-router-dom"; import chat from "./chat.svg"; import TextField from "@material-ui/core/TextField"; import AccountCircleSharpIcon from "@material-ui/icons/AccountCircleSharp"; import FingerprintSharpIcon from "@material-ui/icons/FingerprintSharp"; import io from "socket.io-client"; function Chat() { let [name, setName] = useState(); let [room, setRoom] = useState(); let [childWidth, setChildWidth] = useState({ width: "50vw" }); let [btnColor, setBtnColor] = useState({ fontSize: "4vmax" }); let [joined, setJoined] = useState([]); const ENDPOINT = "http://localhost:5000"; const search = useLocation().search; useEffect(() => { const name1 = new URLSearchParams(search).get("name"); const room1 = new URLSearchParams(search).get("id"); setName(name1); setRoom(room1); if (window.innerWidth <= 1080) { setChildWidth({ width: "100%", height: "100%" }); setBtnColor({ fontSize: "6vmax" }); } // 仅在组件挂载/路由参数变化时创建一次socket实例 const socket = io(ENDPOINT); socket.emit("userName", { name1, room1 }); // 绑定事件监听,使用函数式更新避免依赖joined数组 const handleUserJoin = (payload) => { setJoined(prev => [...prev, payload.name1]); }; socket.on("userName", handleUserJoin); // 清理逻辑:移除监听、断开连接释放资源 return () => { socket.off("userName", handleUserJoin); socket.disconnect(); }; }, [search]); return ( <> <main id="main_chat"> <section id="child_child" style={childWidth}> <header id="header_child"> <div id="username_roomid"> <p id="user_name"> <span id="user_logo"> {" "} <AccountCircleSharpIcon /> </span>{" "} <span id="name_of_user">{name}</span> </p> <p id="room_name"> <span id="room_logo"> <FingerprintSharpIcon /> </span>{" "} <span id="id_of_room">{room}</span> </p> </div> <div id="logo_name"> <img src={chat} alt="聊天logo" /> <h2>R Chat</h2> </div> </header> <section id="msg_area"> {joined.map((value, index) => { return <p key={index}>{value} has joined</p>; })} </section> <footer id="footer_child"> <div id="footer_input_child"> <TextField id="msg_input" label="Enter a message" variant="outlined" fullWidth /> </div> <div id="footer_button_child"> <SendIcon id="send_button" style={btnColor} /> </div> </footer> </section> </main> </> ); } export default Chat;
改动说明
- 移除了组件顶层和冗余的Socket实例创建逻辑,仅在路由参数变化时创建一次连接
- 使用state函数式更新,消除了对joined数组的依赖,避免重复创建事件监听
- 新增组件卸载时的清理逻辑,主动移除事件监听、断开Socket连接释放资源
- 补充了列表渲染的key属性和img标签的alt属性,修复React原生警告
内容的提问来源于stack exchange,提问作者Rohit Sahu
相关产品推荐
相关产品推荐

