lensh / vue-qq

🎨 Vue family bucket with socket.io and express/koa2 , create a web version of mobile QQ, supporting real-time group chat, real-time private chat, special care, shielding chat, smart IP geographic location, real-time display temperature and other QQ core functions
MIT License
917 stars 230 forks source link

使用socket.io遇到的一些坑 #6

Open lensh opened 6 years ago

lensh commented 6 years ago

一、給指定的用户发送消息

为了给指定的用户发送消息,我们需要建立一张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'
          })
      })
 }
MechaGirls commented 5 years ago

刚好碰到这个socket事件重复监听的问题,谢谢。