Sanu0074
@Sanu0074

Как с помощью socket.io отправить сообщение конкретному клиенту если приложение использует cluster и работает в нескольких процессах на разных портах?

Приложение стартует в кластерном режиме, на каждый воркер устанавливаем соединение с сокетом, юзаем редис адаптер:
app.set('port', httpPort);
    let server = http.createServer(app);
    let io = require('./socketServer')(server);
    io.adapter(redis({host: host, port: port}));
app.set('io', io);

тут мы подключаем основной файл socket.io (socketServer), где проходит авторизация сокета и событие on.connection, где в переменную socketID мы получаем id сессии и запоминаем в массив io.clients текущего клиента.
io.sockets.on('connection', (socket) =>{
        var socketID = socket.handshake.user.sid;     
        io.clients[socketID] = socket;
        io.clients[socketID].broadcast.emit('loggedIn',socket.handshake.user.data);

        socket.on('disconnect', () =>{
            delete io.clients[socketID];
        });
});

Перед всем этим стоит nginx с настроенным upstream чтобы организовать "липкие сессии" (как здесь: socket.io/docs/using-multiple-nodes/#nginx-configu...
Далее, когда хотим отправить сообщение конкретному клиенту, уже из контроллера мы по id юзера узнаем его session-id (мы заранее при авторизации храним эти соответствия в redis), и потом так отправляем сообщение:
this.redis.getByMask(`sid_clients:*`,(err,rdbData) =>{
            Async.each(clients,(client,next)=>{
                let sid = `sid_clients:${client}`;
                let currentClient = rdbData[sid];
                if(!currentClient || !this.io.clients[currentClient]) return next();
                this.io.clients[currentClient].emit(event,data);
                return next();
});

Все здорово работает когда мы запускаем приложение в одном процессе, но при запуске в кластере сообщение при коннекте "loggedIn" получают все клиенты на всех процессах, а вот если с конкретного процесса отправить сообщение клиенту который законнектился на другом процессе не получается, т.к. у каждого процесса свой массив io.clients и они всегда отличаются содержимым, поэтому сообщение не доходит до нужного клиента.

Так вот, как правильно реализовать отправку клиенту в кластерном режиме? Как хранить все приконнекченные сокеты в одном месте (может в редисе) чтобы избежать таких ситуаций как у меня? Я думаю что я зря это намутил и я не пойму сути работы io.adapter(redis(...)).

Пока в голову приходит только мысль, когда из конкретного воркера мы хотим сделать `emit` клиенту чей сокет находится на другом воркере, то сначала мы посылаем сообщение в масер процесс (со всеми данными что хотим отослать), в мастере ищем есть ли нужный клиент и на каком он воркере, например через redis мы находим это соответствие, и уже тогда шлем данные из мастера в нужный воркер в котором в `proccess.on` ловим это сообщение из мастера и шлем нужному клиенту.
Но, что тогда будет если приложение будет запущено в кластере на нескольких физических машинах (серверах), всё опять сломается. В целом мне не нравится эта идея, все же мне необходимы советы спецов!
  • Вопрос задан
  • 1151 просмотр
Решения вопроса 2
Sanasol
@Sanasol
нельзя просто так взять и загуглить ошибку
@xfg
Смотрим исходный код библиотеки socket.io-redis, находим там метод Redis#broadcast, внутри метода мы видим, что прежде чем сделать publish в редис канал, там вызывается метод Adapter#broadcast. Отлично, идем смотреть Adapter#broadcast и если отбросить всё лишнее мы увидим суть

if (rooms.length) {
   /*...*/
} else {
  for (var id in self.sids) {
    /*...*/
  }
}


Как видно, socket.io может отправить данные либо в какую-то комнату/комнаты, либо всем клиентам, которые к нему подключены. Увы, но отправить данные конкретному пользователю как видно из кода нельзя.

Но вы можете при подключении для каждого пользователя создавать свою собственную комнату именем которой может быть id пользователя и уже в эту комнату отправлять данные.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы