带有 socket.io 的快递服务器不会发送给正确的收件人

Posted

技术标签:

【中文标题】带有 socket.io 的快递服务器不会发送给正确的收件人【英文标题】:express server with socket.io does not emit to right recipient 【发布时间】:2021-11-22 15:27:41 【问题描述】:

我正在尝试创建视频聊天,并在后端使用 expresssocket.io,在前端使用 reactsimple-peer

这是我的服务器代码:

const http = require('http');
const express = require('express');

const app = express();
const httpServer = http.createServer(app);

app.use(require('cors')());

const io = require('socket.io')(httpServer, 
  cors: 
    origin: ['http://localhost:3000', 'http://localhost:3001'],
    method: ['GET', 'POST']
  
);

const cache = ;

io.on('connection', socket => 
  socket.on('join', id => 
    cache[id] = socket.id;
    console.log('cache', cache);
    socket.join(id);
  );

  socket.on('disconnect', () => 
    socket.broadcast.emit('callEnded');
  );

  socket.on('callUser', data => 
    console.log('calling user', data.userToCall, cache[data.userToCall]);
    io.to(data.userToCall).emit('callUser', 
      signal: data.signalData,
      from: data.from,
      name: data.name
    );
  );

  //   socket.on('answerCall', data => 
  //     console.log('data', data);
  //     io.to(cache[data.to]).emit('callAccepted', data.signal);
  //   );

  socket.on('answerCall', data => 
    console.log('answering call', data);
    io.to(data.to).emit('callAccepted', data.signal);
  );
);

httpServer.listen(4000, () => 'Listening...');

我正在接受来自端口 3000 和 3001 的请求,因为这是我运行我的两个应用程序的地方。由于我目前在后端没有登录系统,因此我正在为每个对等方运行一个应用程序。

第一个应用的代码如下所示:

import  useEffect, useState, useRef  from "react";
import Peer from "simple-peer";
import io from "socket.io-client";
import './App.css';

const ID = 'fey'

function App() 
  const [ stream, setStream ] = useState()
  const [receivingCall, setReceivingCall] = useState(false) ;
  const [caller, setCaller] = useState("") ;
  const [callerSignal, setCallerSignal] = useState() ;
  const [callAccepted, setCallAccepted] = useState(false);
  const [callEnded, setCallEnded] = useState(false);
  const socket = io.connect("http://localhost:4000/");
  const myVideo = useRef()
  const userVideo = useRef()
  const connectionRef = useRef()

  useEffect(() => 
    navigator.mediaDevices.getUserMedia(video: true, audio: true)
    .then((stream)=>
      setStream(stream)
      myVideo.current.srcObject = stream
    )
    .catch((err) => console.log(err))
  
    socket.emit('join', ID)

    socket.on("callUser", (data)=>
      setReceivingCall(true)
      setCaller(data.from)
      setCallerSignal(data.signal)
    )
  , [])


  const callUser = ()=> 
    const peer = new Peer(
       initiator:true,
       trickle:false,
       stream:stream
     )
   
     peer.on("signal", (data)=>
       socket.emit("callUser",
         userToCall: 'fey-clone',
         signalData: data,
         from: ID,
         name: "Fey"
       )
     )
   
   
    peer.on("stream", (stream)=> 
      userVideo.current.srcObject = stream
    )
    
    socket.on("callAccepted", (signal) => 
      console.log('call accepted!!')
      setCallAccepted(true)
      peer.signal(signal)
    )

    connectionRef.current = peer
   

  const answerCall = () => 
    console.log('peer exists')

    setCallAccepted(true)
    const peer = new Peer(
      initiator:false,
      trickle:false,
      stream: stream
    )
  
    peer.on("signal", (data)=> 
      console.log('call answered')
      socket.emit("answerCall", signal:data, to: caller)
    )
  
    peer.on("stream", (stream) =>
      userVideo.current.srcObject = stream
    )
    peer.signal(callerSignal)
    connectionRef.current = peer
  

  const leaveCall = ()=>
    setCallEnded(true)
    connectionRef.current.destroy()
  

  return (
    <div className="App">
      <div className="video">
        stream && <video playsInline muted ref=myVideo autoPlay style=width: "300px", height: "300px"  />
        callAccepted && <video playsInline muted ref=userVideo autoPlay style=width: "300px", height: "300px"  />
      </div>
      callAccepted && !callEnded ? (
         <button onClick=leaveCall>
           End Call
         </button>
       ):(
        <button onClick=callUser>call meeee</button>


       )
      receivingCall && !callAccepted ? (
        <div className="caller">
          <h1>Fey is calling ...</h1>
          <button  onClick=answerCall >
            Answer
          </button>
        </div>
        ) : null
      
    </div>
  );


export default App;

另一个对等点的代码看起来很相似,但 ID 不同,userToCall 是另一个。

import  useEffect, useState, useRef  from "react";
import Peer from "simple-peer";
import io from "socket.io-client";
import './App.css';

const ID = 'fey-clone'

function App() 
  const [ stream, setStream ] = useState()
  const [receivingCall, setReceivingCall] = useState(false) ;
  const [caller, setCaller] = useState("") ;
  const [callerSignal, setCallerSignal] = useState() ;
  const [callAccepted, setCallAccepted] = useState(false);
  const [callEnded, setCallEnded] = useState(false);
  const socket = io.connect("http://localhost:4000/");
  const myVideo = useRef()
  const userVideo = useRef()
  const connectionRef = useRef()

  useEffect(() => 
    navigator.mediaDevices.getUserMedia(video: true, audio: true)
    .then((stream)=>
      setStream(stream)
      myVideo.current.srcObject = stream
    )
    .catch((err) => console.log(err))
  
    socket.emit('join', ID)

    socket.on("callUser", (data)=>
      setReceivingCall(true)
      setCaller(data.from)
      setCallerSignal(data.signal)
    )
  , [])


  const callUser = ()=> 
    const peer = new Peer(
       initiator:true,
       trickle:false,
       stream:stream
     )
   
     peer.on("signal", (data)=>
       socket.emit("callUser",
         userToCall: 'fey',
         signalData: data,
         from: ID,
         name: "fey clone"
       )
     )
   
   
    peer.on("stream", (stream)=> 
      userVideo.current.srcObject = stream
    )
    
    socket.on("callAccepted", (signal) => 
      console.log('call accepted!!')
      setCallAccepted(true)
      peer.signal(signal)
    )

    connectionRef.current = peer
   

  const answerCall = () => 
    setCallAccepted(true)
    const peer = new Peer(
      initiator:false,
      trickle:false,
      stream: stream
    )
  
    peer.on("signal", (data)=> 
      console.log(data, caller)
      socket.emit("answerCall", signal:data, to: caller)
    )
  
    peer.on("stream", (stream) =>
      console.log('I streeeam')
      userVideo.current.srcObject = stream
    )
    peer.signal(callerSignal)
    connectionRef.current = peer
  

  const leaveCall = ()=>
    setCallEnded(true)
    connectionRef.current.destroy()
  

  return (
    <div className="App">
      <div className="video">
        stream && <video playsInline muted ref=myVideo autoPlay style=width: "300px", height: "300px"  />
        callAccepted && <video playsInline muted ref=userVideo autoPlay style=width: "300px", height: "300px"  />
      </div>
      callAccepted && !callEnded ? (
         <button onClick=leaveCall>
           End Call
         </button>
       ):(
        <button onClick=callUser>call meeee</button>


       )
      receivingCall && !callAccepted ? (
        <div className="caller">
          <h1>Fey is calling ...</h1>
          <button onClick=answerCall >
            Answer
          </button>
        </div>
        ) : null
      
    </div>
  );


export default App;

呼叫似乎是通过正确的接收者。当fey 呼叫fey-clone 时,fey-clone 可以接听电话。 signal 似乎也可以正常工作。但是,原来的调用者fey似乎从来没有收到来自服务器的事件callAccepted,所以视频通话无法开始。服务器很可能没有将事件发送给正确的对等方,但我尝试调试无济于事。我在这里有什么遗漏吗?

【问题讨论】:

【参考方案1】:

看起来问题出在客户端的连接上。 连接发生在组件内部,这意味着每次重新渲染组件时都会创建一个新连接。大错特错。

我在客户端的代码现在是这样的:

import React, useEffect, useRef, useState  from "react";
import Peer from "simple-peer";
import io from "socket.io-client";

const socket = io.connect('http://localhost:5000')

function Chat() 
  const [ stream, setStream ] = useState()
  const [ receivingCall, setReceivingCall ] = useState(false)
  const [ caller, setCaller ] = useState("")
  const [ callerSignal, setCallerSignal ] = useState()
  const [ callAccepted, setCallAccepted ] = useState(false)
  const [ idToCall, setIdToCall ] = useState("")
  const [ callEnded, setCallEnded] = useState(false)
  const [ name, setName ] = useState("")
  const myVideo = useRef()
  const userVideo = useRef()
  const connectionRef= useRef()

  useEffect(() => 

    navigator.mediaDevices.getUserMedia( video: true, audio: true )
    .then((stream) => 
      setStream(stream)
        myVideo.current.srcObject = stream
    )

    socket.emit('join', 'fey')

    socket.on("callUser", (data) => 
      setReceivingCall(true)
      setCaller(data.from)
      setName(data.name)
      setCallerSignal(data.signal)
    )
  , [])

  const callUser = (id) => 
    const peer = new Peer(
      initiator: true,
      trickle: false,
      stream: stream
    )

    peer.on("signal", (data) => 
      socket.emit("callUser", 
        userToCall: 'fey-clone',
        signalData: data,
        from: 'fey',
        name: 'fey'
      )
    )

    peer.on("stream", (stream) => 
      userVideo.current.srcObject = stream
    )
    
    socket.on("callAccepted", (signal) => 
      setCallAccepted(true)
      peer.signal(signal)
    )

    connectionRef.current = peer
  

  const answerCall =() =>  
    setCallAccepted(true)
    
    const peer = new Peer(
      initiator: false,
      trickle: false,
      stream: stream
    )

    peer.on("signal", (data) => 
      socket.emit("answerCall",  signal: data, to: caller )
    )

    peer.on("stream", (stream) => 
      userVideo.current.srcObject = stream
    )

    peer.signal(callerSignal)
    connectionRef.current = peer
  

  const leaveCall = () => 
    setCallEnded(true)
    connectionRef.current.destroy()
  

  return (
    <>
      <h1>zoomish</h1>
      <div className="container">
        <div className="video-container">
          <div className="video">
            stream && <video playsInline muted ref=myVideo autoPlay style=width: "300px"  />
          </div>
          <div className="video">
            callAccepted && !callEnded ? 
              <video playsInline ref=userVideo autoPlay style=width:"300px"/>
            : null
          </div>
        </div>
        <div className="myId">
          <div className="call-button">
            callAccepted && !callEnded ? (
              <button onClick=leaveCall>End Call</button>
            ):(
              <button onClick=()=>callUser(idToCall)>call me</button>
            )
          </div>
        </div>
        <div>
        receivingCall && !callAccepted ? (
          <div className="caller">
            <h1>name is calling ...</h1>
            <button onClick=answerCall>Answer</button>
          </div>
        ):null
        </div>
      </div>
    </>
  );


export default Chat;

在服务器上没有什么特别的错误,但是一旦它与socket.join 连接,我就会复制已经分配给套接字的房间,并且不需要这样做。所以我开始维护userIdsocket.id 的地图。在生产应用程序中,这可能会被委托给单独的存储以更好地处理流量。我的服务器现在是这样的:

const express = require("express")
const http = require("http")
const app = express()
const server = http.createServer(app)

const io = require("socket.io")(server, 
  cors: 
    origin: ["http://localhost:3000", "http://localhost:3001" ],
    methods: [ "GET", "POST" ]
  
)

let users = 

io.on("connection", (socket) => 

  socket.on('join', (userId) => 
    users[userId] = socket.id
  );

  socket.on("disconnect", () => 
    socket.broadcast.emit("callEnded")
  )

  socket.on("callUser", (data) => 
    io.to(users[data.userToCall]).emit("callUser",  
      signal: data.signalData,
      from: data.from,
      name: data.name
    )
  )

  socket.on("answerCall", (data) => 
    io.to(users[data.to]).emit("callAccepted", data.signal)
  )

)

server.listen(5000, () => console.log("server is running on port 5000"))

就是这样!

【讨论】:

以上是关于带有 socket.io 的快递服务器不会发送给正确的收件人的主要内容,如果未能解决你的问题,请参考以下文章

Socket.io-client 未连接到 socket.io 服务器反应和节点(快递)

带有 socket.io 的节点服务器不会建立连接

Socket.io 不会在断开连接时向房间发送消息

如何验证来自 socket.io 的数据包?

Java Socket IO不会发送到Node.js,但可以连接和接收

带有django和python socket io的事件