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

React中如何避免Socket.IO发送重复数据?

客户端发送消息后重复接收,服务器端出现MaxListenersExceededWarning警告

当客户端发送message时,服务器端和消息展示容器div都会收到大量重复消息,同时终端出现内存泄漏警告,以下是相关代码及问题详情:

全局Socket Provider Context

为所有子组件提供Socket实例的上下文:

// fileName : SocketProvider.js
import { createContext } from "react";
import { io } from "socket.io-client";
const socket = io("http://localhost:8080/", { withCredentials: true });

const SocketContext = createContext();

export const SocketProvider = ({ children }) => {
  return (
    <SocketContext.Provider value={{ socket }}>
      {children}
    </SocketContext.Provider>
  );
};

export default SocketContext;

App.js中的包裹方式

<SocketProvider>
   <Main/>
</SocketProvider>

主页面(Main.jsx)

包含消息展示区、输入框及发送逻辑:

// filename : Main.jsx
// 所有依赖已正确导入
import ...

const Main = ()=>{
   const { socket } = useContext(SocketContext); 
   const [message, setMessage] = useState("");
   const [conversation, setConversation] = useState([]);
   
   // 其他全局上下文
   const { currentUserId } = useContext(UserContext);
   const { selectedUserId, setSelectedUserId } = useContext(SelectedUserContext);

   // 切换selectedUserId的代码
   // ...
    ...
   .... //

   // 初始化时发送userJoined事件
  useEffect(() => {
    socket.emit("userJoined", {
      senderUserId: currentUserId,
      receiverUserId: selectedUserId,
    });
  }, [socket, currentUserId, selectedUserId]);

 
   // 监听消息的useEffect
   useEffect(() => {
    const onMessageReceived = (msg) => {
      setConversation((conversations) => [...conversations, msg]);
    };

    socket.on("message", onMessageReceived);
    return () => {
      socket.off("message", onMessageReceived);
    };
  }, [socket]);

  // 发送消息函数
  const sendMessage = useCallback(() => {
    if (!message) return;
    socket.emit("message", {
     msg: message,
     sentBy: "Unknown"
    });
    setMessage("");
  }, [socket, message]);

  // 监听回车键发送
  useEffect(() => {
    const handleEnterKeyPressed = (e) => {
      if (e.keyCode === 13 && e.shiftKey === false && e.ctrlKey === false)
        sendMessage();
    };

    element.addEventListener("keydown", handleEnterKeyPressed);
    return () => {
      element.removeEventListener("keydown", handleEnterKeyPressed);
    };
  }, [sendMessage]);

   return (
     <div className="container">
        <div className="msgContainer">
          {conversation.map((data)=>(
             <h1 className="...">{data.msg}</h1>
             <p className="...">{data.sentBy}</p>
          ))}
        </div>
        <input
          value={message}
          onChange={(e)=>setMessage(e.target.value)}
          placeholder="Enter message here"/>
        <img className="..." src={sendIcon} onClick={sendMessage}/>
     </div>
   );
}
export default Main;

服务端监听代码(server.js)

//filename: server.js
io.on("connection", (socket) => {
  socket.on("userJoined", (userJoinedData) => {
     const chatRoomId = generateChatRoomId(userJoinedData);
     socket.on("message", async (msgData) => {
      // 此处会多次打印,次数超过11次
      console.log(`DEBUG: New Message Arrived :`, msgData);
     
      // 保存消息到MongoDB
      const savedMessage = await saveMsg(msgData);
      io.to(chatRoomId).emit("message", savedMessage);
    });

  });

});

终端警告信息

(node:14040) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 message listeners added to [Socket]. Use emitter.setMaxListeners() to increase limit


问题原因及解决方案

核心问题1:服务端重复绑定message事件

每次触发userJoined事件时,都会给当前socket新增一个message事件监听器,导致同一个socket上绑定了多个相同的监听器,收到消息时会多次执行处理逻辑,同时触发内存泄漏警告。

修复方案:
将message监听器移到userJoined外部,通过变量存储当前聊天房间ID,避免重复绑定:

//filename: server.js
io.on("connection", (socket) => {
  let currentChatRoomId = null;
  
  // 只绑定一次message监听器
  socket.on("message", async (msgData) => {
    if (!currentChatRoomId) return;
    console.log(`DEBUG: New Message Arrived :`, msgData);
    const savedMessage = await saveMsg(msgData);
    io.to(currentChatRoomId).emit("message", savedMessage);
  });

  socket.on("userJoined", (userJoinedData) => {
    currentChatRoomId = generateChatRoomId(userJoinedData);
    // 加入目标聊天房间
    socket.join(currentChatRoomId);
  });

  // 断开连接时清理房间ID
  socket.on("disconnect", () => {
    currentChatRoomId = null;
  });
});

核心问题2:客户端优化(潜在体验提升)

虽然客户端的监听清理逻辑无问题,但发送消息时可以先更新本地会话,减少等待服务端响应的延迟:

const sendMessage = useCallback(() => {
  if (!message) return;
  const newMsg = { msg: message, sentBy: "Unknown" };
  // 先更新本地会话,提升体验
  setConversation(prev => [...prev, newMsg]);
  socket.emit("message", newMsg);
  setMessage("");
}, [socket, message]);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 05:39:24