如何在 Node.js 中管理 socket.io 中的用户?
插座。IO 是一个库,支持浏览器和服务器之间的实时、双向和基于事件的通信。
方法:首先,需要注意的是,当一个新的套接字被创建时,它会被分配一个唯一的 id,这个 id 是通过调用 socket.id 来检索的。这个 Id 可以存储在一个用户对象中,我们可以分配一个标识符,比如用户名。对于前端,我们将使用 React,对于后端 node.js 和 express。在主目录名服务器(后端)和客户端(前端)中创建两个文件夹。Socket.on 将是一个在任何需要的时候都会被调用的事件,这个事件由 socket.emit 调用,在这里我们调用这个事件,如果需要的话传递参数。
首先我们将创建应用程序的后端部分:
步骤 1: 为后端安装依赖项
npm init
npm install cors
npm install express
npm install nodemon
npm install socket.io
npm install http
项目结构:如下图。
第二步:创建 Index.js. Socket.on 用于加入每当用户要加入时前端都会发出加入事件,后端会发出消息事件并发送用户已经加入的消息。
index.js 内部使用了三种方法:
- 连接:每当用户将从主页加入房间时,连接事件将被触发,房间中的每个人都将获得该用户已经加入的消息,并且该用户被添加到该房间数据中。
- sendMessage: 现在,当任何一个用户通过聊天中的输入发送消息时,sendMessage 事件将被触发,消息将被添加到带有用户名的消息数组中。
- 断开连接:当用户离开聊天时,断开连接事件将被触发,用户将从该房间的用户列表中移除,并且将发送用户已经离开聊天的消息。
文件名:index.js
java 描述语言
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.`));
步骤 3: 创建一个 User.js 文件,该文件包含以下功能。
这些是存储用户、获取用户、删除用户的功能,用在 index.js 文件中。
- 添加用户:每当建立连接或新用户加入,并且用户名存储在该房间的数组中,并且如果用户名已经被该房间内的任何用户使用,则该用户将无法进入聊天。
- removeUser: 每当连接被破坏或用户离开,用户名从该房间的数组中删除时,都会调用该函数。
- getUser: 该函数以 id 为参数,通过从数组中查找返回用户名。
- getUsersInRoom: 该功能将返回房间内所有用户的姓名,在聊天中显示。
文件名:User.js
java 描述语言
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};
现在我们将处理应用程序的前端部分:
步骤 1: 安装前端反应。
npx create react-app "client"
步骤 2: 反应安装后,在客户端文件夹中安装项目的依赖项。
npm install query-string;
npm install react-emoji;
npm install react-router;
npm install socket.io-client;
项目结构:如下图。
第三步:在 App.js 内部,为页面加入页面和聊天页面创建路由,并导入两个页面的组件以在该路由上显示。
文件名:App.js
java 描述语言
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;
步骤 4: 在内部,socket.emit 调用 join 和其他事件,socket.on 创建从后端调用的事件,比如存储消息、用户数据。这是主要组件,将通过发送参数(如用户名、聊天中显示的消息)来调用所有其他组件。每当用户访问此页面时,都会从后端调用 join 事件。每当消息或用户数据发生变化时,即如果任何用户加入或离开或任何用户发布消息,将调用一个消息事件,并在 useeffect 内部调用 roomdata 事件,以显示用户进入或离开的消息,并将其存储在消息数组中。信息栏将显示房间的名称。文本容器将显示房间中所有用户的姓名。消息将显示聊天中的所有消息。Input 将接受输入,将其存储在数组中,并激发 sendmessage 事件以用户名发送消息。
档案名称:Chat.js
java 描述语言
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";
var connectionOptions = {
"force new connection" : true,
"reconnectionAttempts": "Infinity",
"timeout" : 10000,
"transports" : ["websocket"]
};
var 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;
第五步:创建 Chat.css 为 Chat.js 添加样式
档案名称:Chat.css
半铸钢ˌ钢性铸铁(Cast Semi-Steel)
.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%;
}
}
第六步:在页面的页眉显示房间名称,我们从聊天组件中获取房间名称。
档案名称:InfoPath . js
java 描述语言
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;
第七步:创建 Infobar.css 文件。
档案名称:InfoPath . CSS
半铸钢ˌ钢性铸铁(Cast Semi-Steel)
.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%;
}
第 8 步:用户给出输入并发送消息,然后从消息数组的后端和数据存储中调用 sendmessage 事件。
文件名:Input.js
java 描述语言
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;
第九步:创建 Input.css 文件。
文件名:Input.css
半铸钢ˌ钢性铸铁(Cast Semi-Steel)
.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%;
}
第 10 步:从主页获取用户输入他们的姓名和要加入的房间,当用户加入时,将用户发送到聊天页面。
文件名:Join.js
java 描述语言
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;
第 11 步:创建一个 Join.css 文件,为 Join.js 组件添加样式。
文件名:Join.css
半铸钢ˌ钢性铸铁(Cast Semi-Steel)
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;
}
第 12 步:在聊天框中显示消息,如果消息来自当前用户,那么它将在右侧显示不同的背景颜色,否则它将在左侧显示不同的背景颜色。
文件名:Message.js
java 描述语言
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;
步骤 13: Message.css
半铸钢ˌ钢性铸铁(Cast Semi-Steel)
.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;
}
步骤 14: 从消息数组和消息组件中的名称中逐一发送消息,然后显示给用户。
文件名:Messages.js
java 描述语言
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;
版权属于:月萌API www.moonapi.com,转载请注明出处