Мой стек приложений:
На моем сервере запущен сервер Redis. Бэкэнд PHP взаимодействует с библиотекой Predis с сервером Redis. Он будет публиковать сообщения. Эти сообщения будут получены моим клиентом Redis (node.js) и перенаправлены на подключенные клиенты websocket (с помощью SockJS).
Моя проблема:
Он работает хорошо. По крайней мере, для широковещательных сообщений. Теперь я пришел к тому, что мне нужно отправить одноадресное сообщение, и я застрял... Как подключить пользователя на стороне сервера (отправителя сообщений) с подключенным клиентом веб-узла?
Фрагменты кода:
PHP
$redis = new Client();
$redis->publish('updates', Random::getUniqueString());
Клиент Redis на сервере node.js
redis.subscribe('updates');
redis.on('message', function(channel, data) {
for (var id in sockets) {
if (sockets.hasOwnProperty(id)) {
sockets[id].write(data);
}
}
});
Клиент SockJS
mySocketFactory.setHandler('message', function(event) {
console.log(event.data);
});
Как я и сказал. Хорошо работает, но id
используемый для подключения к сокету, не известен бэкэнд PHP.
Изменение: Одна идея, которую я имел в виду, - это использовать куки.
Я нашел способ решить свою проблему. Когда соединение сокета установлено, я отправил запрос на мой PHP-сервер и попросил идентификатор пользователя. Это сохраняется на сервере node.js. Когда сообщения поступают, есть чек, если они предназначены для конкретного пользователя и обрабатывают их только для них.
Итак, что я точно храню на моем узловом сервере?
var sockets = {}; // {connection_id: socket_connection}
var connIdToUser = {}; // {connection_id: user_id}
var connIdsForUser = {}; // {user_id: [connection_id_1, connection_id_2 ,...]}
socketServer.on('connection', function(conn) {
sockets[conn.id] = conn;
var options = {
host: os.hostname(),
port: 80,
path: '/user/id',
method: 'GET'
};
var req = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
var userId = JSON.parse(chunk).id;
connIdToUser[conn.id] = userId;
if (!connIdsForUser.hasOwnProperty(userId)) {
connIdsForUser[userId] = [];
}
connIdsForUser[userId].push(conn.id);
console.log('connection id ' + conn.id + ' related to user id ' + userId);
});
});
req.end();
conn.on('close', function() {
console.log('connection lost ' + conn.id);
// remove connection id from stack for user
var connections = connIdsForUser[connIdToUser[conn.id]];
var index = connections.indexOf(conn.id);
if (index > -1) {
connections.splice(index, 1);
}
// remove connection at all
delete sockets[conn.id];
// remove relation between connection id and user
delete connIdToUser[conn.id];
});
});
Причина сохранения отношения между идентификатором пользователя дважды ID подключения - это другой вариант использования, который мне нужен либо для отправки сообщения, либо для удаления соединения для события закрытия. В противном случае мне пришлось бы использовать вложенный цикл. Как вы можете видеть, удаление сокета довольно просто. Хотя удаление соединения из стека подключений пользователя немного сложнее.
Продолжайте отправку сообщения. Здесь я определил структуру сообщения, которое я получаю с сервера Redis:
{
targets: [], // array of unit ids (can be empty)
data: <mixed> // the real data
}
Отправка данных в сокеты выглядит так:
redis.on('message', function(channel, message) {
message = JSON.parse(message);
// unicast/multicast
if (message.targets.length > 0) {
message.targets.forEach(function(userId) {
if (connIdsForUser[userId] !== undefined) {
connIdsForUser[userId].forEach(function(connId) {
sockets[connId].write(message.data);
});
}
});
// broadcast
} else {
for (var id in sockets) {
if (sockets.hasOwnProperty(id)) {
sockets[id].write(message.data);
}
}
}
});
Поскольку я храню стек подключения для каждого пользователя, довольно легко отправить данные во все сокеты, относящиеся к конкретному пользователю. Так что теперь я могу сделать unicast (массив с одним идентификатором пользователя), multicast (массив с несколькими идентификаторами пользователя) и широковещательный (пустой массив).
Это хорошо работает для моего использования.