Node.js 如何管理socket.io中的用户
Socket.IO是一个库,可以在浏览器和服务器之间实现实时、双向和基于事件的通信。在Socket.io和Node.js中管理用户通常涉及处理用户连接、断开连接以及向特定用户或用户组广播消息。
前提条件
- React JS
- Node JS
- Socket.io
在Node.js中管理socket.io用户的方法
首先,重要的是要注意,当创建新的socket时,它会被分配一个唯一的id,可以通过调用socket.id获得。这个id可以存储在用户对象中,并且我们可以分配一个识别符,比如用户名。对于前端,我们将使用React,对于后端则使用node.js和express。在主目录中创建两个文件夹,一个是server(后端),一个是client(前端)。当需要时,Socket.on将是一个被调用的事件,并且该事件被socket.emit调用,我们在其中调用事件并传递参数(如果需要)。
创建应用程序的步骤
后端设置
步骤1: 使用命令初始化项目
npm init
JavaScript
步骤2: 安装所需的依赖项:
npm i cors express nodemon socket.io http
JavaScript
后端项目结构
在后端的 package.json 文件中,更新的依赖关系将如下所示:
{
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"http": "^0.0.1-security",
"nodemon": "^3.0.1",
"socket.io": "^4.7.2"
}
}
JavaScript
示例: Socket.on用于在用户加入前端时发出连接事件,后端会发出消息事件并发送用户加入的消息。 **** 定义连接、消息和其他用户功能,如添加、删除和获取用户。
// Filename - index.js
const express = require('express');
const socketio = require('socket.io');
const http = require('http');
const cors = require('cors');
const { addUser, removeUser, getUser,
getUsersInRoom } = require("./users");
const app = express();
const server = http.createServer(app);
const io = socketio(server);
app.use(cors())
io.on("connection", (socket) => {
socket.on('join', ({ name, room }, callback) => {
const { error, user } = addUser(
{ id: socket.id, name, room });
if (error) return callback(error);
// Emit will send message to the user
// who had joined
socket.emit('message', {
user: 'admin', text:
`{user.name},
welcome to room{user.room}.`
});
// Broadcast will send message to everyone
// in the room except the joined user
socket.broadcast.to(user.room)
.emit('message', {
user: "admin",
text: `{user.name}, has joined`
});
socket.join(user.room);
io.to(user.room).emit('roomData', {
room: user.room,
users: getUsersInRoom(user.room)
});
callback();
})
socket.on('sendMessage', (message, callback) => {
const user = getUser(socket.id);
io.to(user.room).emit('message',
{ user: user.name, text: message });
io.to(user.room).emit('roomData', {
room: user.room,
users: getUsersInRoom(user.room)
});
callback();
})
socket.on('disconnect', () => {
const user = removeUser(socket.id);
if (user) {
io.to(user.room).emit('message',
{
user: 'admin', text:
`{user.name} had left`
});
}
})
})
server.listen(process.env.PORT || 5000,
() => console.log(`Server has started.`));
JavaScript
// Filename - User.js
const users = [];
const addUser = ({ id, name, room }) => {
name = name.trim().toLowerCase();
room = room.trim().toLowerCase();
const existingUser = users.find((user) => {
user.room === room && user.name === name
});
if (existingUser) {
return { error: "Username is taken" };
}
const user = { id, name, room };
users.push(user);
return { user };
}
const removeUser = (id) => {
const index = users.findIndex((user) => {
user.id === id
});
if (index !== -1) {
return users.splice(index, 1)[0];
}
}
const getUser = (id) => users
.find((user) => user.id === id);
const getUsersInRoom = (room) => users
.filter((user) => user.room === room);
module.exports = {
addUser, removeUser,
getUser, getUsersInRoom
};
JavaScript
前端设置
步骤1: 在终端中使用以下命令安装React前端
npx create react-app client
JavaScript
步骤2: 在安装了React之后,在client文件夹内安装项目依赖项。
cd client
npm i query-string react-emoji react-router socket.io-client
JavaScript
前端项目结构
前端的 package.json 文件中更新的依赖项如下:
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"query-string": "^8.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-emoji": "^0.5.0",
"react-router": "^6.17.0",
"react-scripts": "5.0.1",
"socket.io-client": "^4.7.2",
"web-vitals": "^2.1.4"
},
JavaScript
步骤3: 在App.js中,为加入页面和聊天页面创建路由,并导入这两个页面的组件以在该路由上显示。
文件名: App.js
// Filename - App.js
import React from 'react';
import Chat from './components/Chat/Chat';
import Join from './components/Join/Join';
import { BrowserRouter as Router, Route }
from "react-router-dom";
const App = () => {
return (
<Router>
<Route path="/" exact component={Join} />
<Route path="/chat" component={Chat} />
</Router>
);
}
export default App;
JavaScript
// Filename - Chat/Chat.js
import React, { useState, useEffect } from "react";
import queryString from "query-string";
import io from 'socket.io-client';
import TextContainer from '../TextContainer/TextContainer';
import Messages from '../Messages/Messages';
import InfoBar from '../InfoBar/InfoBar';
import Input from '../Input/Input';
import "./Chat.css";
let connectionOptions = {
"force new connection": true,
"reconnectionAttempts": "Infinity",
"timeout": 10000,
"transports": ["websocket"]
};
let socket = io.connect('https://localhost:5000', connectionOptions);
const Chat = ({ location }) => {
const [name, setName] = useState('');
const [room, setRoom] = useState("");
const [users, setUsers] = useState('');
const [message, setMessage] = useState('');
const [messages, setMessages] = useState([]);
const ENDPOINT = 'localhost:5000';
useEffect(() => {
const { name, room } = queryString.parse(location.search);
setName(name);
setRoom(room);
socket.emit('join', { name, room }, (error) => {
if (error) {
alert(error);
}
})
return () => {
socket.emit('disconnect');
socket.off();
}
}, [ENDPOINT, location.search]);
useEffect(() => {
socket.on('message', (message) => {
setMessages([...messages, message]);
})
socket.on("roomData", ({ users }) => {
setUsers(users);
});
}, [messages, users])
//Function for Sending Message
const sendMessage = (e) => {
e.preventDefault();
if (message) {
socket.emit('sendMessage', message, () => setMessage(''))
}
}
console.log(message, messages);
return (
<div className="outerContainer">
<div className="container">
<InfoBar room={room} />
<Messages messages={messages} name={name} />
<Input message={message} setMessage={setMessage}
sendMessage={sendMessage} />
</div>
<TextContainer users={users} />
</div>
)
};
export default Chat;
JavaScript
// Filename - InfoBar/InfoBar.js
import React from 'react';
import './InfoBar.css';
const InfoBar = ({ room }) => (
<div className="infoBar">
<div className="leftInnerContainer">
<h3>{room}</h3>
</div>
<div className="rightInnerContainer">
<a href="/">Leave</a>
</div>
</div>
);
export default InfoBar;
JavaScript
// Filename - Input/Input.js
import React from 'react';
import './Input.css';
const Input = ({ setMessage, sendMessage, message }) => (
<form className="form">
<input
className="input"
type="text"
placeholder="Type a message..."
value={message}
onChange={({ target: { value } }) => setMessage(value)}
onKeyPress={event => event.key === 'Enter'
? sendMessage(event) : null}
/>
<button className="sendButton"
onClick={e => sendMessage(e)}>Send</button>
</form>
)
export default Input;
JavaScript
// Filename - Join/Join.js
import React, { useState } from "react";
import { Link } from 'react-router-dom';
import './Join.css';
const Join = () => {
const [name, setName] = useState('');
const [room, setRoom] = useState("");
return (
<div className="joinOuterContainer">
<div className="joinInnerContainer">
<h1 className="heading">Join</h1>
<div>
<input placeholder="Name"
className="joinInput"
type="text"
onChange=
{(event) => setName(event.target.value)} />
</div>
<div>
<input placeholder="Room"
className="joinInput mt-20"
type="text" onChange=
{(event) => setRoom(event.target.value)} />
</div>
<Link onClick={e => (!name || !room) ?
e.preventDefault() : null}
to={`/chat?name={name}&room={room}`
}>
<button className={'button mt-20'}
type="submit">Sign In
</button>
</Link>
</div>
</div>
);
};
export default Join;
JavaScript
// Filename - Messages/Message/Message.js
import React from 'react';
import './Message.css';
import ReactEmoji from 'react-emoji';
const Message = ({ message: { text, user }, name }) => {
let isSentByCurrentUser = false;
const trimmedName = name.trim().toLowerCase();
if (user === trimmedName) {
isSentByCurrentUser = true;
}
return (
isSentByCurrentUser
? (
<div className="messageContainer justifyEnd">
<p className="sentText pr-10">{trimmedName}</p>
<div className="messageBox backgroundBlue">
<p className="messageText colorWhite">
{ReactEmoji.emojify(text)}
</p>
</div>
</div>
)
: (
<div className="messageContainer justifyStart">
<div className="messageBox backgroundLight">
<p className="messageText colorDark">
{ReactEmoji.emojify(text)}
</p>
</div>
<p className="sentText pl-10 ">{user}</p>
</div>
)
);
}
export default Message;
JavaScript
// Filename - Messages/Messages.js
import React from 'react';
import Message from './Message/Message';
import './Messages.css';
const Messages = ({ messages, name }) => (
<div>
{messages.map((message, i) => <div key={i}>
<Message message={message} name={name} />
</div>)}
</div>
);
export default Messages;
JavaScript
// Filename TextContainer/TextContainer.js
import React from "react";
import onlineIcon from "../../icons/onlineIcon.png";
import "./TextContainer.css";
const TextContainer = ({ users }) => (
<div className="textContainer">
{users ? (
<div>
<h1>People currently chatting:</h1>
<div className="activeContainer">
<h2>
{users.map(({ name }) => (
<div
key={name}
className="activeItem"
>
{name}
<img
alt="Online Icon"
src={onlineIcon}
/>
</div>
))}
</h2>
</div>
</div>
) : null}
</div>
);
export default TextContainer;
JavaScript
CSS
/* Filename - Chat/Chat.css */
.outerContainer {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #1A1A1D;
}
.container {
display: flex;
flex-direction: column;
justify-content: space-between;
background: #FFFFFF;
border-radius: 8px;
height: 60%;
width: 35%;
}
@media (min-width: 320px) and (max-width: 480px) {
.outerContainer {
height: 100%;
}
.container {
width: 100%;
height: 100%;
}
}
@media (min-width: 480px) and (max-width: 1200px) {
.container {
width: 60%;
}
}
JavaScript
CSS
/* Filename - InfoBar/InfoBar.css*/
.infoBar {
display: flex;
align-items: center;
justify-content: space-between;
background: #2979FF;
border-radius: 4px 4px 0 0;
height: 60px;
width: 100%;
}
.leftInnerContainer {
flex: 0.5;
display: flex;
align-items: center;
margin-left: 5%;
color: white;
}
.rightInnerContainer {
display: flex;
flex: 0.5;
justify-content: flex-end;
margin-right: 5%;
}
.onlineIcon {
margin-right: 5%;
}
JavaScript
CSS
/* Filename - Input/Input.css */
.form {
display: flex;
border-top: 2px solid #D3D3D3;
}
.input {
border: none;
border-radius: 0;
padding: 5%;
width: 80%;
font-size: 1.2em;
}
input:focus,
textarea:focus,
select:focus {
outline: none;
}
.sendButton {
color: #fff !important;
text-transform: uppercase;
text-decoration: none;
background: #2979FF;
padding: 20px;
display: inline-block;
border: none;
width: 20%;
}
JavaScript
CSS
/* Filename - Join/Join.css*/
html,
body {
font-family: 'Roboto', sans-serif;
padding: 0;
margin: 0;
}
#root {
height: 100vh;
}
* {
box-sizing: border-box;
}
.joinOuterContainer {
display: flex;
justify-content: center;
text-align: center;
height: 100vh;
align-items: center;
background-color: #1A1A1D;
}
.joinInnerContainer {
width: 20%;
}
.joinInput {
border-radius: 0;
padding: 15px 20px;
width: 100%;
}
.heading {
color: white;
font-size: 2.5em;
padding-bottom: 10px;
border-bottom: 2px solid white;
}
.button {
color: #fff !important;
text-transform: uppercase;
text-decoration: none;
background: #2979FF;
padding: 20px;
border-radius: 5px;
display: inline-block;
border: none;
width: 100%;
}
.mt-20 {
margin-top: 20px;
}
@media (min-width: 320px) and (max-width: 480px) {
.joinOuterContainer {
height: 100%;
}
.joinInnerContainer {
width: 90%;
}
}
button:focus {
outline: 0;
}
JavaScript
CSS
/* Filename - Messages/Message/Message.css */
.messageBox {
background: #F3F3F3;
border-radius: 20px;
padding: 5px 20px;
color: white;
display: inline-block;
max-width: 80%;
}
.messageText {
width: 100%;
letter-spacing: 0;
float: left;
font-size: 1.1em;
word-wrap: break-word;
}
.messageText img {
vertical-align: middle;
}
.messageContainer {
display: flex;
justify-content: flex-end;
padding: 0 5%;
margin-top: 3px;
}
.sentText {
display: flex;
align-items: center;
font-family: Helvetica;
color: #828282;
letter-spacing: 0.3px;
}
.pl-10 {
padding-left: 10px;
}
.pr-10 {
padding-right: 10px;
}
.justifyStart {
justify-content: flex-start;
}
.justifyEnd {
justify-content: flex-end;
}
.colorWhite {
color: white;
}
.colorDark {
color: #353535;
}
.backgroundBlue {
background: #2979FF;
}
.backgroundLight {
background: #F3F3F3;
}
JavaScript
CSS
/* Filename Messages/Messages.css */
.messages {
padding: 5% 0;
overflow: auto;
flex: auto;
}
JavaScript
CSS
/* Filename - TextContainer/TextContainer.css */
.textContainer {
display: flex;
flex-direction: column;
margin-left: 100px;
color: rgb(201, 25, 25);
height: 60%;
justify-content: space-between;
}
.activeContainer {
display: flex;
align-items: center;
margin-bottom: 50%;
}
.activeItem {
display: flex;
align-items: center;
}
.activeContainer img {
padding-left: 10px;
}
.textContainer h1 {
margin-bottom: 0px;
}
@media (min-width: 320px) and (max-width: 1200px) {
.textContainer {
display: none;
}
}
JavaScript
运行后端步骤: 进入后端文件夹并打开终端,输入以下命令。
npm start
JavaScript
运行前端的步骤: 进入文件夹并打开终端,输入以下命令。
npm start
JavaScript
输出: 打开浏览器,并输入 localhost 3000来查看应用程序运行。