先看效果视频:
websocket聊天
我们可以看到是一个模仿微信的一个聊天功能。
使用到的技术:socket.id
前端:socket.id-client
后端:socket.id
官网地址:Socket.IO
主要是通过socketio中,客户端可以通过socket发送和接受服务器端传递的数据,服务端也可以socket获取到客户端发送和传递的数据。通过相互传递数据从而实现聊天功能
一、先讲解客户端和服务端如何通过socketio发送接受数据:
1、服务端
通过下载插件socket.io以后
我们通过下载http,创建一个新的服务,使该服务使用到我们后端express中
请注意,我通过传递(HTTP 服务器)对象来初始化 的新实例。然后,我侦听传入套接字的事件并将其记录到控制台。socket.io
server
connection
const http = require('http');
const express = require('express');
const { Server } = require('socket.io');
const app = express();
const httpServer = http.createServer(app);
const io = new Server(httpServer, {
cors: {
origin: "*"
}
});
(1)、为了向所有人发送事件,Socket.IO 给了我们方法:io.emit()
io.emit('some event', { someProperty: 'some value', otherProperty: 'other value' }); // This will emit the event to all connected sockets
(2)、如果您想向除某个发射套接字之外的所有人发送消息,我们有从该套接字发出的标志:broadcast:
发送给除了自己意外的所有人:
io.on('connection', (socket) => {
socket.broadcast.emit('hi');
});
(3)、消息发送给所有人,包括发件人
io.on('connection', (socket) => {
socket.on('chat message', (msg) => {
io.emit('chat message', msg);
});
});
(4)、私法给某个人
socket.to(socket.id).emit('sendMessage', message)
每个新连接都分配有一个随机的 20 个字符的标识符。
此标识符与客户端的值同步。
// server-side
io.on("connection", (socket) => {
console.log(socket.id); // ojIckSD2jqNzOqIrAGzL
});
// client-side
socket.on("connect", () => {
console.log(socket.id); // ojIckSD2jqNzOqIrAGzL
});
创建后,Socket 会加入由其自己的 id 标识的房间,这意味着您可以将其用于私人消息传递:
io.on("connection", socket => {
socket.on("private message", (anotherSocketId, msg) => {
socket.to(anotherSocketId).emit("private message", socket.id, msg);
});
});
注意:您不能覆盖此标识符,因为它在 Socket.IO 代码库的多个部分中使用。
我们可以通过获取到的socket.id进行发送给某人数据
2、客户端
在使用时,我们尽量保证只有需要socket通信的页面从而进行socket接口获取(为了导致只有使用的那个页面才会与后端建立连接)
前端通过下载socket.io-client插件以后,定义socket变量为我们的指定后端服务器的地址
import io from 'socket.io-client';
//js代码
const socket = io('http://localhost:3000');// 指定服务器的URL
通过socket我们可以实现与后端建立连接。
该属性描述套接字当前是否连接到服务器。
socket.on("connect", () => {
console.log(socket.connected); // true
});
socket.on("disconnect", () => {
console.log(socket.connected); // false
});
通过emit给服务端发送数据,通过on接受服务端传递的数据
socket.emit('userData', userData)
socket.on('userid', (id) => {//获取后端传递的值})
后端定义自定义发送数据,前端只需要通过获取的时候与后端传递的第一个参数定义名保持一致即可接受和获取数据
//前端
socket.on('sendMsg', (msg) => {
console.log(msg, '群发');
})
//后端
io.emit('sendMsg', receiveUser.name + ':' + message)
通过disconnect()可以退出连接
socket.disconnect()
二、实现微信聊天通信的思路
1、前端页面大致布局:
登录页面完成跳转到Home主页面,Home主页面有两个子路由(Message,Phone)分别是消息路由以及通讯录路由
点击不同的好友会跳到详情页面Detail
我们通信的基本思路都在详情页面
2、后端使用express+mongoose+socket.id
通过服务器的搭建,并且将socketio的流程也放置在express当中
(1)、发送数据时会通过调用接口,将我们的数据发送给后端,但是需要传递两个id以及消息(接受消息人的id,发送消息人的id以及发送的消息)给后端,后端通过数据获取将发送着与被发送者创建一个消息表,里边是发送者的id以及接收者的id以及消息message,我们最后需要通过该接口将消息渲染到页面
//发送消息 客户端
const handleSend = async () => {
// socket.emit('message', { partner: data.data[0], user: data.data[1], message: iptValue })
let message = inp.current.value
let { data } = await axios.get(`http://localhost:3333/getMessage?rid=${partner._id}&sid=${userData._id}&message=${message}`)
if (data.code == 200) {
socket.emit('message', { receiveUser: data.data[0], sendUser: data.data[1], message: message });
inp.current.value = ''
getrecord()
}
};
//接受前端消息发送到数据库 服务端
router.get('/getMessage', async (req, res) => {
let reveiveId = req.query.rid
let sendId = req.query.sid
let message = req.query.message
console.log(reveiveId, '接收者', sendId, '发送者');
let reveiveUser = await userModel.find({ _id: reveiveId })
let sendUser = await userModel.find({ _id: sendId })
await recordModel.create({ sendUser: sendId, receiverUser: reveiveId, message: message, reveiveimg: reveiveUser[0].imgurl, sendimg: sendUser[0].imgurl })
res.send({
code: 200,
msg: "获取成功",
data: [reveiveUser[0], sendUser[0]]
})
})
(2)、进入详情页面更改数据库用户id为我们新生成的socketid,由于socketio进行私发互发消息时,需要通过后端连接生成的一个字符作为发送的凭借。我们每次进入页面都得更新id否则下次发送消息将会发送不到。
//修改用户表中的id值
let changeId = async (id) => {
userData.userid = id
console.log(userData, 'userData');
await axios.post('http://localhost:3333/changeId', userData)
}
socket.on('connect', () => {
socket.emit('userData', userData)
socket.on('userid', (id) => {
changeId(id)
console.log(id);
})
})
io.on('connection', (socket) => {
console.log('一个用户已进入');
let userSocketId = socket.id;
console.log(userSocketId, '1234');
// 接收客户端发送的消息
socket.on('userData', async (data) => {
//data为前端发送的用户的个人信息对象
socket.emit('userid', userSocketId)
});
})
(3)、接受到消息以后,前端定义请求后端消息接口获取消息数据,从而渲染到页面。由于发送消息为了防止页面不实施刷新需要在发送消息时、初始化进入页面时、进行私发消息以后调用获取信息数据的接口。
这里我们通过发送数据时,将发送者的_id以及接受者的_id以及消息发送到后端,后端通过将发送者的_id和接受者的_id保存到我们的数据库,我们获取消息数据时,通过将两个用户的_id依然传递到后端,后端通过查找消息表,通过发送者的_id以及接受者的_id进行查找,如果存在接受者和发送者的id返回给前端(是为了将我们发送者与被发送者发送的数据全部拿到前端,前端可以通过发送者id以及被发送者id从而渲染到页面)。
(4)、最后渲染到页面。由于发送者数据在左侧,接收者数据在右侧,因此样式上需要进行处理
<div className="chat-messages">
{/* 消息区域 */}
{
record.map((item, index) => {
return (
<div className='detailChat-item' key={index}>
{
item.sendUser === userData._id ?
<div className='oneself' style={{ textAlign: 'right' }}>
<div className="mes_box">
<span>{item.message}</span>
<div className='sanjiao'></div>
</div>
<img className='imgs' src={userData.urlimg} alt="" />
</div>
:
<div className='otherSelf' style={{ textAlign: 'left' }}>
<img className='imgs' src={partner.urlimg} alt="" />
<div className="mes_box">
<span>{item.message}</span>
<div className='sanjiao'></div>
</div>
</div>
}
</div>
)
})
}
</div>
.mes_con>.chat-window>.chat-messages {
/* background: red; */
width: 100%;
height: 81vh;
overflow-y: scroll;
}
.detailChat>.detailChat-top {
width: 100vw;
position: fixed;
top: 0;
}
.rv-nav-bar__left>svg {
font-size: 22px;
color: black;
}
.rv-nav-bar__right>svg {
font-size: 22px;
color: black;
}
.detailChat-message {
margin-top: 50px;
}
.detailChat-item>p>img {
width: 50px;
height: 50px;
border-radius: 50%;
}
.detailChat-bottom {
border-top: 1px solid #ccc;
width: 100vw;
height: 5rem;
position: fixed;
bottom: 0;
}
.detailChat-bottom>.rv-input {
height: 3rem;
}
最后展示全部代码(只展示有关socketio代码):
前端:
const socket = io('http://localhost:3000');// 指定服务器的URL
const location = useLocation()
const navigate = useNavigate()
// 对象数据
let partner = location.state.data || [];
// 当前用户数据
let userData = JSON.parse(sessionStorage.getItem('user')) || []
// 保存聊天记录
// console.log(partner, userData);
const [record, setRecord] = useState([])
const inp = useRef()
const handleSubmit = (event) => {
event.preventDefault();
// 在这里可以处理发送消息的逻辑,比如将消息发送到服务器
};
//发送消息
const handleSend = async () => {
// socket.emit('message', { partner: data.data[0], user: data.data[1], message: iptValue })
let message = inp.current.value
let { data } = await axios.get(`http://localhost:3333/getMessage?rid=${partner._id}&sid=${userData._id}&message=${message}`)
if (data.code == 200) {
socket.emit('message', { receiveUser: data.data[0], sendUser: data.data[1], message: message });
inp.current.value = ''
getrecord()
}
};
//修改用户表中的id值
let changeId = async (id) => {
userData.userid = id
console.log(userData, 'userData');
await axios.post('http://localhost:3333/changeId', userData)
}
// 获取聊天记录
const getrecord = async () => {
let { data } = await axios.get(`http://localhost:3333/getrecord?user1=${partner._id}&user2=${userData._id}`)
setRecord(data.data)
console.log(data.data, '消息数据');
}
const goBack = () => {
navigate('/home/message');
socket.disconnect()
}
//监听滚动条事件
const scrollHandle = (e) => {
console.log(e.target.scrollTop, e.target.scrollHeight, e.target.clientHeight);
}
//点击进入详情页面保存更改用户的id
useEffect(() => {
getrecord()
console.log(record);
//partner接收者 userData发送者
// console.log(partner, userData);
socket.on('connect', () => {
socket.emit('userData', userData)
socket.on('userid', (id) => {
changeId(id)
console.log(id);
})
})
socket.on('sendMsg', (msg) => {
console.log(msg, '群发');
})
socket.on('sendMessage', (message) => {
// console.log(message, '私发');
getrecord()
})
return () => {
socket.emit('leave', userData)
socket.disconnect()
}
}, [])
// 监听新消息保持在最底部--- 获取最后一条消息
useEffect(() => {
let lasrChild = document.querySelector('.chat-messages').lastElementChild
console.log(lasrChild);
if (lasrChild) {
lasrChild.scrollIntoView(false)
}
}, [record])
html:
<div className="chat-window">
<div className="header_mes_con">
<h2>{location.state.data.name}</h2>
<button onClick={goBack} style={{ position: 'absolute', top: '20px', right: '10px' }}>退出聊天</button>
</div>
{/* onScroll={(e) => { scrollHandle(e) }} */}
<div className="chat-messages">
{/* 消息区域 */}
{
record.map((item, index) => {
return (
<div className='detailChat-item' key={index}>
{
item.sendUser === userData._id ?
<div className='oneself' style={{ textAlign: 'right' }}>
<div className="mes_box">
<span>{item.message}</span>
<div className='sanjiao'></div>
</div>
<img className='imgs' src={userData.urlimg} alt="" />
</div>
:
<div className='otherSelf' style={{ textAlign: 'left' }}>
<img className='imgs' src={partner.urlimg} alt="" />
<div className="mes_box">
<span>{item.message}</span>
<div className='sanjiao'></div>
</div>
</div>
}
</div>
)
})
}
</div>
<div className="chat-input">
{/* 在这里放置聊天消息展示区域 */}
<form onSubmit={handleSubmit} style={styles.container}>
<input
type="text"
ref={inp}
placeholder="在这里输入消息..."
style={styles.input}
/>
<button type="submit" style={styles.button} onClick={handleSend}>
发送
</button>
</form>
</div>
</div>
后端:
//引入包
const http = require('http');
const express = require('express');
const { Server } = require('socket.io');
const { userModel, recordModel } = require('../model/model');
var router = express.Router();
//接口
router.post('/login', async (req, res) => {
let loginData = req.body
console.log(loginData);
let userData = await userModel.find({ username: loginData.username, password: loginData.password })
if (userData.length != 0) {
res.send({
code: 200,
msg: "登录成功",
userData: userData[0],
})
} else {
res.send({
code: 400,
msg: "用户名或密码错误",
})
}
})
router.get('/getuser', async (req, res) => {
let data = await userModel.find()
res.send({
code: 200,
msg: '数据获取成功',
data
})
})
const app = express();
const httpServer = http.createServer(app);
const io = new Server(httpServer, {
cors: {
origin: "*"
}
});
// 在线用户列表
io.on('connection', (socket) => {
console.log('一个用户已进入');
let userSocketId = socket.id;
console.log(userSocketId, '1234');
// 接收客户端发送的消息
socket.on('userData', async (data) => {
//data为前端发送的用户的个人信息对象
socket.emit('userid', userSocketId)
});
//发送接受一个消息
socket.on('message', ({ receiveUser, sendUser, message }) => {
// console.log(receiveUser, sendUser, message);
console.log(receiveUser.userid, '接受人的userid')
let id = receiveUser.userid
// io.emit('sendMsg', receiveUser.name + ':' + message)
socket.to(receiveUser.userid).emit('sendMessage', message)
})
socket.on('disconnect', () => {
console.log('一个用户退出');
});
// 离开时 获取到的 用户信息
socket.on('leave', (data) => {
console.log('离开啦', data);
})
});
httpServer.listen(3000, () => {
console.log('Server is running on port 3000');
});
// 设置当前用的userid
router.post('/changeId', async (req, res) => {
let user = req.body;
let userid = req.body.userid;
// console.log(user, 'user');
await userModel.updateOne({ _id: user._id }, { userid: userid })
res.send({
code: 200,
msg: "保存userID完成"
})
})
//接受前端消息发送到数据库
router.get('/getMessage', async (req, res) => {
let reveiveId = req.query.rid
let sendId = req.query.sid
let message = req.query.message
console.log(reveiveId, '接收者', sendId, '发送者');
let reveiveUser = await userModel.find({ _id: reveiveId })
let sendUser = await userModel.find({ _id: sendId })
await recordModel.create({ sendUser: sendId, receiverUser: reveiveId, message: message, reveiveimg: reveiveUser[0].imgurl, sendimg: sendUser[0].imgurl })
res.send({
code: 200,
msg: "获取成功",
data: [reveiveUser[0], sendUser[0]]
})
})
//获取消息
router.get('/getrecord', async (req, res) => {
let user1 = req.query.user1;
let user2 = req.query.user2;
let data = await recordModel.find({ sendUser: { $in: [user1, user2] }, receiverUser: { $in: [user1, user2] } })
res.send({
code: 200,
msg: "获取成功",
data
})
})