Nodejs如何使用socket.io做到上线提醒?

Nodejs如何使用socket.io做到上线提醒?

场景如下: 1万个用户,分布在不同的node.js server中,当你登录时,如何通知到你的好友,你已经上线?

我的问题是: 1.如何知道你的好友已经上线? 2.如何发送消息给你的好友(他可能在另外的server中)?

13 回复

Nodejs如何使用socket.io做到上线提醒?

场景描述

假设有一个应用场景,有1万个用户分布在多个Node.js服务器上。当一个用户登录时,需要通知其好友,该用户已上线。这个问题主要涉及两个方面:

  1. 如何知道你的好友已经上线?
  2. 如何将上线通知发送给你的所有好友(他们可能分布在不同的服务器上)?

解决方案

为了解决上述问题,我们可以使用Socket.IO来实现实时通信。Socket.IO是一个可以在浏览器和服务器之间实现实时、双向通信的库。

1. 使用Redis进行消息传递

由于用户分布在不同的Node.js服务器上,我们需要一个中间件来协调消息传递。这里我们使用Redis作为消息队列,因为它支持发布/订阅模式。

2. 实现步骤
  1. 安装依赖

    npm install express socket.io redis
    
  2. 设置Redis客户端

    const redis = require('redis');
    const subscriber = redis.createClient();
    const publisher = redis.createClient();
    
  3. 创建Socket.IO服务器

    const express = require('express');
    const http = require('http');
    const socketIo = require('socket.io');
    
    const app = express();
    const server = http.createServer(app);
    const io = socketIo(server);
    
    io.on('connection', (socket) => {
      console.log('a user connected:', socket.id);
    
      // 当用户上线时,订阅他们的频道
      socket.on('login', (userId) => {
        socket.join(userId); // 将用户加入到对应的频道
        console.log(`${userId} has logged in`);
      });
    
      // 处理上线通知
      socket.on('onlineNotification', (data) => {
        const { userId, friendId } = data;
        io.to(friendId).emit('onlineNotification', { userId });
      });
    });
    
    server.listen(3000, () => {
      console.log('listening on *:3000');
    });
    
  4. 处理好友上线通知

    subscriber.on('message', (channel, message) => {
      const data = JSON.parse(message);
      io.to(data.friendId).emit('onlineNotification', data);
    });
    
    subscriber.subscribe('onlineChannel');
    
  5. 前端代码示例

    <script src="/socket.io/socket.io.js"></script>
    <script>
      const socket = io();
    
      // 用户登录时调用
      function login(userId) {
        socket.emit('login', userId);
      }
    
      // 发送上线通知
      function sendOnlineNotification(userId, friendId) {
        socket.emit('onlineNotification', { userId, friendId });
      }
    </script>
    

通过上述步骤,我们可以实现用户登录时的实时通知功能。每个用户登录后会进入一个特定的频道,而其他用户可以通过订阅这些频道来接收上线通知。这样即使用户分布在不同的服务器上,也可以实现高效的实时通信。


很有意思的问题。我是这么想的:

三层结构

外层:browser 客户端 用 socket.io client 中层:nodejs web server 运行 socket.io server 和 socket.io client 内层:nodejs web server 运行 socket.io server

外层 socket.io client 和 中层 socket.io server 交互 中层 socket.io client 和 内层 socket.io server 交互

结构不是问题,现在的问题是 效率的问题。

现在大部分的例子都是在单机的情况下,当有用户上线时,直接io.sockets.broadcast群发一个消息,但这是个广播消息,效率太低,特别是 在使用 socket.io + redis pub/sub的情况下,一个用户的上下线,就给整个系统是群发一个消息,真是个悲剧.

我自己的想法是 用redis或memerycache 缓存用户的好友列表和全系统的在线用户信息(在线用户信息中包含当前所在server的信息,如IP地址),如果用户的好友列表中有用户在线,则可以找到该在线好友所在的socket.io server的IP地址,将上线消息emit到好友所在的server,好友所在的server收到消息之后,根据该台server中缓存的socket列表,最终可以找到该好友对应的socket连接,并将消息发送出去。

兜了老大的一个圈子,就是为了避免去使去使用全网的broadcast,至于点对点的消息(如:你发送消息给你的好友),这个倒是可以使用 socket.io+redis pub/sub轻松解决,关键是这种通知类的信息比较麻烦,不晓得大家伙有没有更加好的办法?

通知类的消息可以单独系统出来做吧,利用消息队列来下发信息,这样对原系统就影响不大了吧。

在楼主好友上线这个例子,没有必要全网的broadcast。但一个用户上线后,只要查找该用户已经上线的好友,通知这些好友用户就可以了。

比如用 rabbitMQ 之类的异步消息系统。nodejs 有 rabbitMQ 的模块。

请问这个最后是怎么解决的呢?我们也遇到了相同的问题,求解!是采用这个方法吗? “用redis或memerycache 缓存用户的好友列表和全系统的在线用户信息(在线用户信息中包含当前所在server的信息,如IP地址),如果用户的好友列表中有用户在线,则可以找到该在线好友所在的socket.io server的IP地址,将上线消息emit到好友所在的server,好友所在的server收到消息之后,根据该台server中缓存的socket列表,最终可以找到该好友对应的socket连接,并将消息发送出去。”

socket.io还在研究中,不过还是觉得查找好友进行通知比较好,lz说的方案太绕了

楼主可以研究下Meteor。

看看网易那个pomelo聊天的例子吧,客户端连接存到session里面,有新用户登录时候就给所有session发送消息就行了。

为了实现一个基于Node.js和socket.io的系统来实现实时上线提醒功能,我们需要确保所有服务器能够互相通信,并且可以识别出特定用户的上线事件。这里是一种可能的解决方案:

服务器端代码

首先,确保每个Node.js服务器实例都安装了socket.io,并能够通过某种方式(如redis或mqtt)进行相互之间的通信。

步骤1: 设置全局事件总线

我们可以使用redis作为消息队列,或者选择其他合适的消息代理(如mqtt、rabbitmq等)。这样可以在多个Node.js服务器之间共享实时状态。

npm install socket.io redis

步骤2: 创建Socket.IO服务

const io = require('socket.io')(httpServer, {
    cors: {
        origin: '*',
    }
});

const redisClient = require('redis').createClient();

io.on('connection', (socket) => {
    console.log('User connected');

    socket.on('register', (userId) => {
        // 将用户加入到在线列表
        redisClient.sadd('online-users', userId);
    });

    socket.on('disconnect', () => {
        // 用户断开连接时,从在线列表中移除
        redisClient.smembers('online-users', (err, users) => {
            if (users && users.includes(socket.userId)) {
                redisClient.srem('online-users', socket.userId);
            }
        });
    });
});

步骤3: 发送上线通知

当用户上线时,发送一个通知给其好友:

socket.on('login', (userId) => {
    // 当用户上线时,通知所有好友
    redisClient.smembers(`friends:${userId}`, (err, friends) => {
        friends.forEach(friendId => {
            redisClient.get(`socketId:${friendId}`, (err, socketId) => {
                if(socketId) {
                    io.to(socketId).emit('newOnlineUser', { userId });
                }
            });
        });
    });
});

客户端代码

客户端需要在成功登录后向服务器发送登录事件,并监听上线通知。

const socket = io();

// 登录成功后
socket.emit('login', userId);

// 监听好友上线通知
socket.on('newOnlineUser', (data) => {
    console.log(`${data.userId} is online`);
});

以上代码展示了如何通过Redis来管理在线状态,并在用户上线时通知其好友。这种方式可以扩展到更大规模的应用中。

回到顶部