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

express server with socket.io does not emit to right recipient

我正在尝试创建视频聊天并在后端使用 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,所以视频通话无法开始。看起来很可能是服务器没有向正确的对等方发送事件,但我尝试调试无济于事。我在这里遗漏了什么吗?

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

我在客户端的代码现在看起来像这样:

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"))

就是这样!