Open
Description
一、給指定的用户发送消息
为了给指定的用户发送消息,我们需要建立一张hash表,键为用户的id,值为该用户连接时生成的socketid。并把这张表以json的格式存储在服务端的users.json文件里。每次登陆的时候就把用户的id和socketid存储到服务器端。具体怎么实现可以参考问题二里的服务端代码。
二、用户手动刷新或者重新打开浏览器,原来的socketid会失效
如果用户手动刷新或者重新打开浏览器(都可视为刷新),那么服务器端保存的该用户对应的socketid会失效,因为刷新会导致connection,会生成新的socketid。这样导致的问题就是服务端无法继续给指定的用户推送消息了(socketid失效)。
解决方案如下:
客户端:每次连接服务端的时候就emit用户的id。当然得先判断下用户是否已经登陆了,没有登陆的话,自然无法获取用户的id,无法emit,这种情况就只能等用户登陆了再emit用户的id。
客户端需要在两个地方emit用户的id:
(1)登录时(emit用户id)
//登陆后的回调
callback({code,data,message}){
if(code==1){
socket.emit('login',data.loginStatus.userId) // emit login事件,传递用户id
this.$store.commit('SET_LOGIN',data) //设置store状态
this.$router.push('message')
}else{
this.$store.dispatch('setShowWarn',message)
}
}
(2)连接时(需判断是否已经登陆)
<script src="http://localhost:3000/socket.io/socket.io.js"></script>
<script>
const socket = io.connect('http://localhost:3000/')
//解决用户手动刷新浏览器后,原来的socketid失效的问题
const loginStatus= JSON.parse(localStorage.getItem("loginStatus") || '{}')
// emit update事件,传递用户id
loginStatus.isLogin && socket.emit('update',loginStatus.userId)
</script>
服务端:同时监听login事件和update事件
import socketHander from './socket' //socket要实现的具体逻辑
//这里省略其它导入
// socket事件
io.on('connection', (socket) => {
const socketId=socket.id
//监听用户登录
socket.on('login', (userId) => {
//保存用户的id和socketid
socketHander.saveUserSocketId(userId, socketId)
})
//监听用户刷新
socket.on('update', (userId) => {
//保存用户的id和socketid
socketHander.saveUserSocketId(userId, socketId)
})
})
socket.js:
import {
readFile,
writeFile
} from './fs-async'
const filePath = `${__dirname}/users.json`
// socket具体业务逻辑
export default class socketHander {
/**
* [saveUserSocketId 保存用户的id和socketid]
* @param {[type]} userId [用户id]
* @param {[type]} socketId [用户的socketid]
* @return {[type]} [description]
*/
static async saveUserSocketId(userId, socketId) {
let data = await readFile(filePath).catch((err) => {
console.log(err)
})
data[userId] = socketId
writeFile(filePath, data)
}
}
fs-async.js:
import fs from 'fs'
/**
* [读取文件的内容]
* @param {[type]} filePath [文件路径]
* @return {[type]} [description]
*/
export const readFile = (filePath) => {
return new Promise((reslove, reject) => {
fs.readFile(filePath, 'utf8', (err, data) => {
if (!err) {
reslove(JSON.parse(data))
} else {
reject(err)
}
})
})
}
/**
* [写文件]
* @param {[type]} filePath [文件路径]
* @param {[type]} data [新的文件数据]
* @return {[type]} [description]
*/
export const writeFile = (filePath, data) => {
return new Promise((reslove, reject) => {
fs.writeFile(filePath, JSON.stringify(data), (err) => {
if (err) {
reject(err)
}
})
})
}
users.json:
{}
三、socket事件重复监听的问题
在单页应用里,使用socket.on监听事件前一定要先移除原来的事件。不然会导致生成重复的监听器。
//通过socket来更新消息
updateBySocket(){
socket.removeAllListeners() //一定要先移除原来的事件,否则会有重复的监听器
socket.on('receivePrivateMessage',(data)=>{
this.$store.commit('UPDATE_MESSAGE',{
from_user:data.from_user_beizhu,
id:data.from_user,
imgUrl:data.from_user_face,
message:data.message,
time:data.time,
type:'single'
})
})
socket.on('receiveGroupMessage',(data)=>{
//如果不包含自己,则直接丢弃这个socket消息
if(!data.group_member.includes(this.userId-0)) return
this.$store.commit('UPDATE_MESSAGE',{
from_user:data.group_name,
id:data.group_id,
imgUrl:data.group_avator,
message:`${data.from_user_nick_name}:${data.message}`,
time:data.time,
type:'group'
})
})
}
Metadata
Assignees
Labels
No labels
Activity