正确的socket.io实现

huangapple go评论85阅读模式
英文:

correct socket.io implementation

问题

我正在尝试在我的项目中使用Socket.io来显示在线好友,我想知道(实际上,对我来说看起来有点奇怪),每当我尝试重新渲染页面时(无论用户是否更改其个人资料或发送好友请求),负责初始化Socket的useEffect将会断开连接并重新连接。

嗯...我同意如果页面正在刷新或关闭,Socket应该关闭,但不应在用户发送好友请求或更改其个人资料时关闭。我刚刚开始包括Socket和功能还不完整,但到目前为止,在开始阶段,出现这些各种重新连接和Socket的创建很奇怪。此外,我的主要关注点出现在重新渲染时;Socket将被断开连接(这是正常的),但不会重新连接(这是异常的),但我只希望在实际刷新或注销时出现这种行为。

文件(客户端): socket.js:

import { io } from 'socket.io-client';

export const initializeSocket = (token, userInfoQuery, setOnlineFriends, setSocket) => {
  if (token && userInfoQuery) {
    const socket = io(process.env.REACT_APP_API_URL, { auth: { token } });

    const friendIds = userInfoQuery.friends
    socket.emit("userFriends", friendIds);

    socket.on("onlineUsers", (users) => {
      setOnlineFriends(users);
    });

    socket.on("addFriend", (friend) => {
      console.log("Received friend request:", friend);
    });

    socket.on("removeFriend", (friend) => {
      console.log("Received friend request:", friend);
    });

    setSocket(socket);
    return socket;
  }
  return null;
};

文件(客户端): App.js:

export default function App() {
  const { token, logout, login } = useContext(AuthContext);
  const [onlineFriends, setOnlineFriends] = useState([]);
  const [socket, setSocket] = useState(null);

  const userInfoQuery = useQuery("userInfo", async () => {
    const response = await axios.get(
      `${process.env.REACT_APP_API_URL}/api/user`,
      {
        headers: {
          Authorization: "Bearer " + token,
        },
      }
    );
    return response.data;
  });
  const userBooksQuery = useQuery("userBooks", async () => {
    const response = await axios.get(
      `${process.env.REACT_APP_API_URL}/api/user/books`,
      {
        headers: {
          Authorization: "Bearer " + token,
        },
      }
    );
    return response.data;
  });

  useEffect(() => {
    if (socket) {
      socket.connect();
    }
    if (token && userInfoQuery.data && !socket) {
      initializeSocket(token, userInfoQuery.data, setOnlineFriends, setSocket);
    }

    return () => {
      if (socket) {
        socket.disconnect();
      }
    };
  }, [token, userInfoQuery.data, socket]);

  return (...
  )
}

正如您所见,我在一个useEffect中初始化了Socket和所有其功能,以及依赖项数组。如果没有将userInfoQuery作为依赖项,页面将不会重新渲染,因为在线好友是通过Socket获取的。

文件(服务器): socketController.js:

const knexConfig = require("../knexfile");
const { knex } = require("knex");
const db = knex(knexConfig);
const io = require("socket.io");
const jwt = require("jsonwebtoken");
require("dotenv").config();

module.exports.socketController = (io) => {
  const onlineFriends = new Map();

  io.on("connection", async (socket) => {
    console.log(`A user connected ${socket.id}`);
    try {
      const token = socket.handshake.auth.token;
      const decoded = jwt.verify(token, process.env.JWT_SIGN_KEY);
      const userId = decoded.user_id;

      await db("user").where("user_id", userId).update({ is_online: 1 });
      const user = await db("user").where("user_id", userId).first();

      onlineFriends.set(userId, user);

      socket.join(userId);

      const onlineFriendList = Array.from(onlineFriends.values());
      socket.emit("onlineUsers", onlineFriendList);

      socket.on("userFriends", async (friendsList) => {
        friendsList.forEach((friend) => {
          socket.join(friend.friend);
        });

        const onlineUsers = await db("user")
          .whereIn(
            "user_id",
            friendsList.map((friend) => friend.friend)
          )
          .andWhere("is_online", 1);

        onlineUsers.forEach((user) => onlineFriends.set(user.user_id, user));
        const updatedOnlineFriendList = Array.from(onlineFriends.values());

        socket.broadcast.emit("onlineUsers", updatedOnlineFriendList);
      });
      socket.on("addFriend", async (friendId) => {
        const recipientUser = onlineFriends.get(friendId);
        if (recipientUser) {
          const recipientSocket = io.sockets.sockets.get(recipientUser.socket_id);
          if (recipientSocket) {
            recipientSocket.emit("addFriend", userId);
          }
        }
      });
      socket.on("removeFriend", async (friendId) => {
        const recipientUser = onlineFriends.get(friendId);
        if (recipientUser) {
          const recipientSocket = io.sockets.sockets.get(recipientUser.socket_id);
          if (recipientSocket) {
            recipientSocket.emit("removeFriend", userId);
          }
        }
      });
      socket.on("disconnect", async () => {
        await db("user").where("user_id", userId).update({ is_online: 0 });

        onlineFriends.delete(userId);
        const updatedOnlineFriendList = Array.from(onlineFriends.values());

        socket.broadcast.emit("onlineUsers", updatedOnlineFriendList);

        console.log(`${socket.id} disconnected`);
      });

    } catch (error) {
      console.error("Authentication error:", error.message);
      socket.disconnect();
    }
  });
};

我觉得服务器端在返回在线好友用户方面基本正常。如果服务器端存在问题或您有建议,请随时告诉我...

每个用户对象看起来像这样:

{
    avatar_image: "https://i.pravatar.cc/150?img=13",
    email: "ross001@gmail.com",
    favorite_genre: "comedy",
    first_name: "ross",
    friends: Array [ {...} ],
    goal_set: 9,
    last_name: "test",
    username: "rosss001",
}

尽管在useEffect中效率不高,但我尝试通过socket.connect()来尝试重新连接Socket,但似乎不起作用。此外,当用户更改其个人资料时,我

英文:

I'm trying out socket io for my project to show online friends, and I was wondering (actually, it looks kind of strange to me) that whenever I try to rerender the page (it doesn't matter if a user changes his profile info or send a friend req) the useEffect which is in charge of initializing the socket will disconnect and reconnect.

Well... I agree that if the page is being refreshed or closed, the socket should be closed, but not whenever a user sends a friend request or changes his profile info. I just started to include the socket and the functionality is not full but so far at the beginning, having these various reconnections and creation of Sockets is strange. Plus, my main concern shows itself on rerender; the socket will be disconnected (which is normal) and won't connect back (which is abnormal), but I only want this behavior on actual refreshes or logouts.

File (Client): socket.js:

import { io } from 'socket.io-client';
export const initializeSocket = (token, userInfoQuery, setOnlineFriends, setSocket) => {
if (token && userInfoQuery) {
const socket = io(process.env.REACT_APP_API_URL, { auth: { token } });
const friendIds = userInfoQuery.friends
socket.emit("userFriends", friendIds);
socket.on("onlineUsers", (users) => {
setOnlineFriends(users);
});
socket.on("addFriend", (friend) => {
console.log("Received friend request:", friend);
});
socket.on("removeFriend", (friend) => {
console.log("Received friend request:", friend);
});
setSocket(socket);
return socket;
}
return null;
};

File (Client): App.js:

export default function App() {
const { token, logout, login } = useContext(AuthContext);
const [onlineFriends, setOnlineFriends] = useState([]);
const [socket, setSocket] = useState(null);
const userInfoQuery = useQuery("userInfo", async () => {
const response = await axios.get(
`${process.env.REACT_APP_API_URL}/api/user`,
{
headers: {
Authorization: "Bearer " + token,
},
}
);
return response.data;
});
const userBooksQuery = useQuery("userBooks", async () => {
const response = await axios.get(
`${process.env.REACT_APP_API_URL}/api/user/books`,
{
headers: {
Authorization: "Bearer " + token,
},
}
);
return response.data;
});
useEffect(() => {
if (socket) {
socket.connect();
}
if (token && userInfoQuery.data && !socket) {
initializeSocket(token, userInfoQuery.data, setOnlineFriends, setSocket);
}
return () => {
if (socket) {
socket.disconnect();
}
};
}, [token, userInfoQuery.data, socket]);
return ( ...
)

As you can see, I initiated the socket and all its functionality on one useEffect and those dep array. Not having the userInfoQuery as dep would not rerender the page since online friends are coming from the socket.

File (Server): socketController.js:

const knexConfig = require("../knexfile");
const { knex } = require("knex");
const db = knex(knexConfig);
const io = require("socket.io");
const jwt = require("jsonwebtoken");
require("dotenv").config();
module.exports.socketController = (io) => {
const onlineFriends = new Map();
io.on("connection", async (socket) => {
console.log(`A user connected ${socket.id}`);
try {
const token = socket.handshake.auth.token;
const decoded = jwt.verify(token, process.env.JWT_SIGN_KEY);
const userId = decoded.user_id;
await db("user").where("user_id", userId).update({ is_online: 1 });
const user = await db("user").where("user_id", userId).first();
onlineFriends.set(userId, user);
socket.join(userId);
const onlineFriendList = Array.from(onlineFriends.values());
socket.emit("onlineUsers", onlineFriendList);
socket.on("userFriends", async (friendsList) => {
friendsList.forEach((friend) => {
socket.join(friend.friend);
});
const onlineUsers = await db("user")
.whereIn(
"user_id",
friendsList.map((friend) => friend.friend)
)
.andWhere("is_online", 1);
onlineUsers.forEach((user) => onlineFriends.set(user.user_id, user));
const updatedOnlineFriendList = Array.from(onlineFriends.values());
socket.broadcast.emit("onlineUsers", updatedOnlineFriendList);
});
socket.on("addFriend", async (friendId) => {
const recipientUser = onlineFriends.get(friendId); 
if (recipientUser) {
const recipientSocket = io.sockets.sockets.get(recipientUser.socket_id); 
if (recipientSocket) {
recipientSocket.emit("addFriend", userId);
}
}
});
socket.on("removeFriend", async (friendId) => {
const recipientUser = onlineFriends.get(friendId);
if (recipientUser) {
const recipientSocket = io.sockets.sockets.get(recipientUser.socket_id); 
if (recipientSocket) {
recipientSocket.emit("removeFriend", userId);
}
}
});
socket.on("disconnect", async () => {
await db("user").where("user_id", userId).update({ is_online: 0 });
onlineFriends.delete(userId);
const updatedOnlineFriendList = Array.from(onlineFriends.values());
socket.broadcast.emit("onlineUsers", updatedOnlineFriendList);
console.log(`${socket.id} disconnected`);
});
} catch (error) {
console.error("Authentication error:", error.message);
socket.disconnect();
}
});
};

I feel the server-side is pretty much fine in terms of sending back the online friend users. again if the server-side had some problem or you had suggestions on it, feel free to let me know...

each user object if like this :

{
avatar_image: "https://i.pravatar.cc/150?img=13"
​​email: "ross001@gmail.com"
​​favorite_genre: "comedy"
​​first_name: "ross"
​​friends: Array [ {…} ]
​goal_set: 9
last_name: "test"​​
username: "rosss001"
}

Although not being efficient in useEffect, I tried to do socket.connect() to attempt to reconnect to the socket, but it seems not to work. Also, when a user changes his profile info, I tried to in my component refetch the data to trigger the useEffect by : queryClient.refetchQueries("userInfo"); . again it seems it rerenders and disconnects the whole socket and causes the problem altogether. on the other hand, I need to rerender the page to inform the user and the changes made to his account! The socket.connect() at the beginning of useEffect was my ultimate try...

答案1

得分: 0

以下是翻译好的部分:

主要问题在于您的useEffect中的依赖项,每当依赖项的值发生变化时,您的App组件都会重新渲染。这意味着useEffect中的返回函数会执行。为了解决这个问题,请尝试将您的套接字逻辑和用户逻辑分离。

import { io } from 'socket.io-client';

const SocketContext = createContext();

export default function App() {
  const { token, logout, login } = useContext(AuthContext);
  const [socket, setSocket] = useState(null);

  useEffect(() => {
    if (token && !socket) {
      const socket = io(process.env.REACT_APP_API_URL, { auth: { token } });

      socket.on("connect", () => {
        setSocket(socket);
      });
    }

    return () => {
      if (socket) {
        socket.disconnect();
      }
    };
  }, [token, socket]);

  if (!socket) return <p>Loading</p>;

  return (
    <SocketContext.Provider value={{ socket }}>
      <UserControlComponent />
    </SocketContext.Provider>
  );
}

以上代码为您的UserComponent提供了已连接的io-socket对象。使用socket对象,您可以执行任何操作。

示例:

function UserControlComponent() {
  const { socket } = useContext(SocketContext);
  const { token } = useContext(AuthContext);
  const [onlineFriends, setOnlineFriends] = useState([]);

  const userInfoQuery = useQuery("userInfo", async () => {
    const response = await axios.get(
      `${process.env.REACT_APP_API_URL}/api/user`,
      {
        headers: {
          Authorization: "Bearer " + token,
        },
      }
    );
    return response.data;
  });

  const userBooksQuery = useQuery("userBooks", async () => {
    const response = await axios.get(
      `${process.env.REACT_APP_API_URL}/api/user/books`,
      {
        headers: {
          Authorization: "Bearer " + token,
        },
      }
    );
    return response.data;
  });

  useEffect(() => {
    socket.on("onlineUsers", (users) => {
      setOnlineFriends(users);
    });

    socket.on("addFriend", (friend) => {
      console.log("Received friend request:", friend);
    });

    socket.on("removeFriend", (friend) => {
      console.log("Received friend request:", friend);
    });
  }, [socket]);

  return (
    <p>Your user logic</p>
  );
}

请尝试在事件处理程序中使用socket.emit("signal", data)

英文:

Main problem here is dependencies in your useEffect, whenever your dependencies values changes your App component is re-rendering. This means your return function in useEffect is executing. To solve this problem try seperating your socket logic and user logic

import { io } from &#39;socket.io-client&#39;;
const SocketContext = createContext()
export default function App() {
const {token, logout, login} = useContext(AuthContext);
const [socket, setSocket] = useState(null);
useEffect(() =&gt; {
if (token &amp;&amp; !socket) {
const socket = io(process.env.REACT_APP_API_URL, { auth: { token } });
socket.on(&quot;connect&quot;, () =&gt; {
setSocket(socket);
})
}
return () =&gt; {
if (socket) {
socket.disconnect();
}
};
}, [token, socket]);
if (!socket) return &lt;p&gt;Loading&lt;/p&gt;
return (
&lt;SocketContext.Provider value={{socket}}&gt;
&lt;UserControlComponent/&gt;
&lt;/SocketContext.Provider&gt;
)
}

Code given above provides your UserComponent with already connected io-socket object. With socket object you can do any action you want.

Example:

function UserControlComponent() {
const {socket} = useContext(SocketContext)
const {token} = useContext(AuthContext);
const [onlineFriends, setOnlineFriends] = useState([]);
const userInfoQuery = useQuery(&quot;userInfo&quot;, async () =&gt; {
const response = await axios.get(
`${process.env.REACT_APP_API_URL}/api/user`,
{
headers: {
Authorization: &quot;Bearer &quot; + token,
},
}
);
return response.data;
});
const userBooksQuery = useQuery(&quot;userBooks&quot;, async () =&gt; {
const response = await axios.get(
`${process.env.REACT_APP_API_URL}/api/user/books`,
{
headers: {
Authorization: &quot;Bearer &quot; + token,
},
}
);
return response.data;
});
useEffect(() =&gt; {
socket.on(&quot;onlineUsers&quot;, (users) =&gt; {
setOnlineFriends(users);
});
socket.on(&quot;addFriend&quot;, (friend) =&gt; {
console.log(&quot;Received friend request:&quot;, friend);
});
socket.on(&quot;removeFriend&quot;, (friend) =&gt; {
console.log(&quot;Received friend request:&quot;, friend);
});
}, [socket])
return (
&lt;p&gt;Your user logic&lt;/p&gt;
)
}

And try to use socket.emit("signal", data) in event handlers

huangapple
  • 本文由 发表于 2023年5月26日 11:00:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76337390.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定