当前位置: 首页 > 知识库问答 >
问题:

使用带插座的挂钩。io状态在套接字处理程序中不是持久的

宁兴修
2023-03-14

我有以下反应组件

function ConferencingRoom() {
    const [participants, setParticipants] = useState({})
    console.log('Participants -> ', participants)

    useEffect(() => {
        // messages handlers
        socket.on('message', message => {
            console.log('Message received: ' + message.event)
            switch (message.event) {
                case 'newParticipantArrived':
                    receiveVideo(message.userid, message.username)
                    break
                case 'existingParticipants':
                    onExistingParticipants(
                        message.userid,
                        message.existingUsers
                    )
                    break
                case 'receiveVideoAnswer':
                    onReceiveVideoAnswer(message.senderid, message.sdpAnswer)
                    break
                case 'candidate':
                    addIceCandidate(message.userid, message.candidate)
                    break
                default:
                    break
            }
        })
        return () => {}
    }, [participants])

    // Socket Connetction handlers functions

    const onExistingParticipants = (userid, existingUsers) => {
        console.log('onExistingParticipants Called!!!!!')

        //Add local User
        const user = {
            id: userid,
            username: userName,
            published: true,
            rtcPeer: null
        }

        setParticipants(prevParticpants => ({
            ...prevParticpants,
            [user.id]: user
        }))

        existingUsers.forEach(function(element) {
            receiveVideo(element.id, element.name)
        })
    }

    const onReceiveVideoAnswer = (senderid, sdpAnswer) => {
        console.log('participants in Receive answer -> ', participants)
        console.log('***************')

        // participants[senderid].rtcPeer.processAnswer(sdpAnswer)
    }

    const addIceCandidate = (userid, candidate) => {
        console.log('participants in Receive canditate -> ', participants)
        console.log('***************')
        // participants[userid].rtcPeer.addIceCandidate(candidate)
    }

    const receiveVideo = (userid, username) => {
        console.log('Received Video Called!!!!')
        //Add remote User
        const user = {
            id: userid,
            username: username,
            published: false,
            rtcPeer: null
        }

        setParticipants(prevParticpants => ({
            ...prevParticpants,
            [user.id]: user
        }))
    }

    //Callback for setting rtcPeer after creating it in child component
    const setRtcPeerForUser = (userid, rtcPeer) => {
        setParticipants(prevParticpants => ({
            ...prevParticpants,
            [userid]: { ...prevParticpants[userid], rtcPeer: rtcPeer }
        }))
    }

    return (
            <div id="meetingRoom">
                {Object.values(participants).map(participant => (
                    <Participant
                        key={participant.id}
                        participant={participant}
                        roomName={roomName}
                        setRtcPeerForUser={setRtcPeerForUser}
                        sendMessage={sendMessage}
                    />
                ))}
            </div>
    )
}

它拥有的唯一状态是调用中使用useState钩子定义的参与者的hashTable。

然后,我使用useEffect来监听聊天室的套接字事件—只有4个事件

然后在那之后,我定义了4个回调处理程序,这些事件与服务器上执行的顺序有关

最后,我有另一个回调函数,它传递给列表中的每个子参与者,以便在子组件创建其rtcPeer对象后,将其发送给父组件,以便在参与者的hashTable中的参与者对象上设置它

流程如下:参与者加入房间-

第一个事件状态为空,随后的两个事件状态为空,然后它再次为空,此模式不断重复一个空状态,然后接下来的两个是正确的,我不知道状态发生了什么

共有1个答案

朱刚捷
2023-03-14

困难的是,您遇到了几个相互影响的问题,这些问题使您的故障排除变得混乱。

最大的问题是您正在设置多个套接字事件处理程序。每次重新渲染,您都在调用socket.on而没有调用socket.off

关于如何处理这一问题,我可以想象有三种主要方法:

>

  • 设置单个套接字事件处理程序,并且只对参与者状态使用功能更新。使用这种方法,您将使用一个空的依赖数组来表示use效应,并且不会在效果中的任何地方引用参与者(包括消息处理程序调用的所有方法)。如果您引用参与者,那么一旦第一次重新渲染发生,您将引用它的旧版本。如果参与者需要发生的更改可以使用功能更新轻松完成,那么这可能是最简单的方法。

    参与者的每次更改设置一个新的套接字事件处理程序。为了使其正常工作,您需要删除以前的事件处理程序,否则您将拥有与呈现相同数量的事件处理程序。当您有多个事件处理程序时,创建的第一个事件处理程序将始终使用第一个版本的参与者(空),第二个事件处理程序将始终使用第二个版本的参与者,等等。这将起作用,并为您如何使用现有的参与者状态提供更多的灵活性,但是有一个缺点,就是反复拆卸和设置套接字事件处理程序,这让人感觉很笨拙。

    设置单个套接字事件处理程序,并使用ref访问当前的参与者状态。这与第一种方法类似,但添加了一个附加效果,该效果在每次渲染时执行,以将当前参与者状态设置为ref,以便消息处理程序可以可靠地访问该状态。

    无论使用哪种方法,如果您将消息处理程序移出呈现函数并显式传递其依赖项,我认为您都可以更轻松地推理代码正在执行的操作。

    第三个选项提供了与第二个选项相同的灵活性,同时避免了重复设置套接字事件处理程序,但在管理参与者sref时增加了一点复杂性。

    下面是第三个选项的代码(我没有尝试执行这个,所以我不能保证我没有轻微的语法问题):

    const messageHandler = (message, participants, setParticipants) => {
      console.log('Message received: ' + message.event);
    
      const onExistingParticipants = (userid, existingUsers) => {
        console.log('onExistingParticipants Called!!!!!');
    
        //Add local User
        const user = {
          id: userid,
          username: userName,
          published: true,
          rtcPeer: null
        };
    
        setParticipants({
          ...participants,
          [user.id]: user
        });
    
        existingUsers.forEach(function (element) {
          receiveVideo(element.id, element.name)
        })
      };
    
      const onReceiveVideoAnswer = (senderid, sdpAnswer) => {
        console.log('participants in Receive answer -> ', participants);
        console.log('***************')
    
        // participants[senderid].rtcPeer.processAnswer(sdpAnswer)
      };
    
      const addIceCandidate = (userid, candidate) => {
        console.log('participants in Receive canditate -> ', participants);
        console.log('***************');
        // participants[userid].rtcPeer.addIceCandidate(candidate)
      };
    
      const receiveVideo = (userid, username) => {
        console.log('Received Video Called!!!!');
        //Add remote User
        const user = {
          id: userid,
          username: username,
          published: false,
          rtcPeer: null
        };
    
        setParticipants({
          ...participants,
          [user.id]: user
        });
      };
    
      //Callback for setting rtcPeer after creating it in child component
      const setRtcPeerForUser = (userid, rtcPeer) => {
        setParticipants({
          ...participants,
          [userid]: {...participants[userid], rtcPeer: rtcPeer}
        });
      };
    
      switch (message.event) {
        case 'newParticipantArrived':
          receiveVideo(message.userid, message.username);
          break;
        case 'existingParticipants':
          onExistingParticipants(
              message.userid,
              message.existingUsers
          );
          break;
        case 'receiveVideoAnswer':
          onReceiveVideoAnswer(message.senderid, message.sdpAnswer);
          break;
        case 'candidate':
          addIceCandidate(message.userid, message.candidate);
          break;
        default:
          break;
      }
    };
    
    function ConferencingRoom() {
      const [participants, setParticipants] = React.useState({});
      console.log('Participants -> ', participants);
        const participantsRef = React.useRef(participants);
        React.useEffect(() => {
            // This effect executes on every render (no dependency array specified).
            // Any change to the "participants" state will trigger a re-render
            // which will then cause this effect to capture the current "participants"
            // value in "participantsRef.current".
            participantsRef.current = participants;
        });
    
      React.useEffect(() => {
        // This effect only executes on the initial render so that we aren't setting
        // up the socket repeatedly. This means it can't reliably refer to "participants"
        // because once "setParticipants" is called this would be looking at a stale
        // "participants" reference (it would forever see the initial value of the
        // "participants" state since it isn't in the dependency array).
        // "participantsRef", on the other hand, will be stable across re-renders and 
        // "participantsRef.current" successfully provides the up-to-date value of 
        // "participants" (due to the other effect updating the ref).
        const handler = (message) => {messageHandler(message, participantsRef.current, setParticipants)};
        socket.on('message', handler);
        return () => {
          socket.off('message', handler);
        }
      }, []);
    
      return (
          <div id="meetingRoom">
            {Object.values(participants).map(participant => (
                <Participant
                    key={participant.id}
                    participant={participant}
                    roomName={roomName}
                    setRtcPeerForUser={setRtcPeerForUser}
                    sendMessage={sendMessage}
                />
            ))}
          </div>
      );
    }
    

    此外,下面是一个工作示例,模拟上述代码中发生的情况,但不使用socket,以清楚显示使用参与者参与者SREF之间的区别。观察控制台并单击这两个按钮,查看将参与者传递给消息处理程序的两种方式之间的差异。

    import React from "react";
    
    const messageHandler = (participantsFromRef, staleParticipants) => {
      console.log(
        "participantsFromRef",
        participantsFromRef,
        "staleParticipants",
        staleParticipants
      );
    };
    
    export default function ConferencingRoom() {
      const [participants, setParticipants] = React.useState(1);
      const participantsRef = React.useRef(participants);
      const handlerRef = React.useRef();
      React.useEffect(() => {
        participantsRef.current = participants;
      });
    
      React.useEffect(() => {
        handlerRef.current = message => {
          // eslint will complain about "participants" since it isn't in the
          // dependency array.
          messageHandler(participantsRef.current, participants);
        };
      }, []);
    
      return (
        <div id="meetingRoom">
          Participants: {participants}
          <br />
          <button onClick={() => setParticipants(prev => prev + 1)}>
            Change Participants
          </button>
          <button onClick={() => handlerRef.current()}>Send message</button>
        </div>
      );
    }
    

  •  类似资料:
    • 我正在开发一个在线的,基于文本的RPG(Github);目前我有一个PHP后端,它将会话数据存储在redis服务器中。对于需要实时通信的所有内容(聊天,消息传递和连接用户列表),我使用Node.js和 socket.io 用于websocket。 我目前在我的网络索克服务器上有3个命名空间: 消息服务器 在线服务器 聊天服务器 我已经让它工作了,但恐怕大部分是由“黑客”制作的。现在,我无法向特定的

    • 我是Heroku的新手,我正在尝试部署使用socket.io侦听的NodeJS应用程序。我能够部署到本地和它的工作罚款。但是当在Heroku中运行相同的内容时,它没有响应。 在Heroku中有没有需要为套接字io指定的特定端口?我不能使用process.env.port,因为我正在将其用于app server端口。 有什么解决这个问题的想法吗?或者我们需要启用一些配置来使用套接字IO吗? 我想我们

    • 我正在努力正确配置nginx,以确保它可以处理Express(端口8081)和Socket的代理。io(端口3000)。下面是我的配置,它当前为整个请求(不仅仅是Socket.io)产生了502错误: 据我所知,我需要确保Socket使用的Websocket。io已升级到HTTP,但这正是我努力掌握自己需要做什么的地方。可能是两个插座。io和Express需要在不同的端口上运行,然后需要根据我上面

    • 我面临的问题是: < li >当我加载页面时,服务器的控制台中会显示套接字(我可以获取远程地址和远程端口) < li >无论我做什么,客户端中的连接总是挂起。我尝试了Chrome、Brave、Mozilla和Safari,但似乎没有一个能用。 我错过了什么?我试图在连接准备好之前不发送消息,但我从未在我的PC中获得过该状态。即使我在没有事件的情况下建立onopen属性,客户端中的警报也不会弹出。

    • 问题内容: 我正在尝试新的React Hooks,并有一个带有计数器的Clock组件,该计数器应该每秒增加一次。但是,该值不会增加到超过一。 问题答案: 原因是传递给的闭包中的回调仅访问第一个渲染器中的变量,而无法访问后续渲染器中的新值,因为第二次未调用。 回调中的值始终为0 。 就像您熟悉的状态挂钩一样,状态挂钩有两种形式:一种是处于更新状态的状态,另一种是将当前状态传入的回调形式。您应该使用第

    • 问题内容: 假设我有一个Java程序,该程序使用HTTP 1.1在服务器上发出HTTP请求,并且不关闭连接。我提出一个请求,并读取从绑定到套接字的输入流返回的所有数据。但是,在发出第二个请求时,服务器没有响应(或者流有问题- 它不再提供任何输入)。如果我按顺序发出请求(请求,请求,读取),则可以正常工作,但(请求,读取,请求,读取)则不能。 有人能解释为什么会发生这种情况吗?(代码片段如下)。无论