Надсилання повідомлення певним підключеним користувачам за допомогою webSocket?


82

Я написав код для трансляції повідомлення для всіх користувачів:

// websocket and http servers
var webSocketServer = require('websocket').server;

...
...
var clients = [ ];

var server = http.createServer(function(request, response) {
    // Not important for us. We're writing WebSocket server, not HTTP server
});
server.listen(webSocketsServerPort, function() {
  ...
});

var wsServer = new webSocketServer({
    // WebSocket server is tied to a HTTP server. 
    httpServer: server
});

// This callback function is called every time someone
// tries to connect to the WebSocket server
wsServer.on('request', function(request) {
...
var connection = request.accept(null, request.origin); 
var index = clients.push(connection) - 1;
...

Зверніть увагу:

  • У мене немає жодного посилання на користувача, а лише підключення.
  • Усі підключення користувачів зберігаються в array.

Мета : Скажімо, сервер Node.js хоче надіслати повідомлення певному клієнту (Джон). Звідки сервер NodeJs дізнається, яке з'єднання має Джон? Сервер Node.js навіть не знає Джона. все, що він бачить, - це зв’язки.

Таким чином, я вважаю , що зараз, я не повинен зберігати користувачів тільки їх зв'язку, замість цього, мені потрібно зберігати об'єкт, який буде містити userIdі на connectionоб'єкт.

Ідея:

  • Коли сторінка завершиться завантаження (DOM готовий) - встановіть підключення до сервера Node.js.

  • Коли сервер Node.js прийме підключення - згенеруйте унікальний рядок і надішліть його клієнтському браузеру. Зберігайте підключення користувача та унікальний рядок в об’єкті. напр{UserID:"6", value: {connectionObject}}

  • На стороні клієнта, коли надходить це повідомлення, зберігайте його у прихованому полі або файлі cookie. (для майбутніх запитів до сервера NodeJs)


Коли сервер хоче надіслати повідомлення Джону:

  • Знайдіть у словнику ідентифікатор користувача John та надішліть повідомлення за відповідним з’єднанням.

  • будь ласка, зверніть увагу, що тут немає коду сервера asp.net (у механізмі повідомлень). лише NodeJ. *

Питання:

Це правильний шлях?

Відповіді:


77

Це не тільки правильний шлях, але єдиний шлях. В основному для кожного з’єднання потрібен унікальний ідентифікатор. Інакше ви не зможете їх ідентифікувати, це так просто.

Тепер те, як ви це представлятимете, це зовсім інша річ. Створення об'єкта з idі connectionвластивостями є хорошим способом , щоб зробити це (я б точно піти на це). Ви також можете приєднати idбезпосередньо до об'єкта підключення.

Також пам’ятайте, що якщо ви хочете спілкуватися між користувачами, вам доведеться також надіслати ідентифікатор цільового користувача, тобто коли користувач A хоче надіслати повідомлення користувачеві B, тоді, очевидно, A повинен знати ідентифікатор B.


1
@RoyiNamir Знову ж таки, у вас немає великого вибору. Браузер перерве підключення, тому вам доведеться обробляти відключення та повторне підключення. Крім того, ви, ймовірно, захочете застосувати якийсь механізм тайм-ауту (знищити об'єкт-обгортку на стороні сервера лише після тайм-ауту), щоб уникнути непотрібних повідомлень, коли це трапиться (припускаючи, що ви хочете повідомити інших користувачів про відключення).
чудернацький

1
@RoyiNamir Що стосується двох унікальних імен: інша ідея полягає в тому, щоб зберегти об'єкт SOCKETS = {};на стороні сервера і додати сокет до списку для даного ідентифікатора, тобто SOCKETS["John"] = [];і SOCKETS["John"].push(conn);. Ймовірно, це вам знадобиться, оскільки користувач може відкривати кілька вкладок.
чудернацький

8
@freakish: Обережно, це лише гарне рішення для однопроцесного додатка. Якщо ви масштабуєте свою програму за допомогою декількох процесів (або серверів), вони не матимуть спільної пам'яті - тому ітерація з використанням локально збережених об'єктів не буде працювати. Краще повторити всі локальні підключення безпосередньо, зберігаючи UUID на самому підключенні. Крім того, для масштабування я б рекомендував Redis.
Myst

1
Підкажіть, будь ласка, як надіслати повідомлення певному клієнту за допомогою об’єкта підключення? Я зберіг ідентифікатор користувача в об'єкті з'єднання, як зазначено вище. Я просто хочу знати, як використовувати об'єкт підключення під час надсилання повідомлення з сервера певному клієнту (у мене є ідентифікатор та об'єкт підключення цього користувача). Дякую.
Вардан

1
@AsishAP, це довга дискусія ... Я рекомендую вам почати з читання цієї статті, щоб орієнтуватися і, можливо, шукати ТАК для інших питань, ось один, який варто прочитати .
Myst

39

Ось простий приватний / прямий обмін повідомленнями на сервері чату.

package.json

{
  "name": "chat-server",
  "version": "0.0.1",
  "description": "WebSocket chat server",
  "dependencies": {
    "ws": "0.4.x"
  }
}

server.js

var webSocketServer = new (require('ws')).Server({port: (process.env.PORT || 5000)}),
    webSockets = {} // userID: webSocket

// CONNECT /:userID
// wscat -c ws://localhost:5000/1
webSocketServer.on('connection', function (webSocket) {
  var userID = parseInt(webSocket.upgradeReq.url.substr(1), 10)
  webSockets[userID] = webSocket
  console.log('connected: ' + userID + ' in ' + Object.getOwnPropertyNames(webSockets))

  // Forward Message
  //
  // Receive               Example
  // [toUserID, text]      [2, "Hello, World!"]
  //
  // Send                  Example
  // [fromUserID, text]    [1, "Hello, World!"]
  webSocket.on('message', function(message) {
    console.log('received from ' + userID + ': ' + message)
    var messageArray = JSON.parse(message)
    var toUserWebSocket = webSockets[messageArray[0]]
    if (toUserWebSocket) {
      console.log('sent to ' + messageArray[0] + ': ' + JSON.stringify(messageArray))
      messageArray[0] = userID
      toUserWebSocket.send(JSON.stringify(messageArray))
    }
  })

  webSocket.on('close', function () {
    delete webSockets[userID]
    console.log('deleted: ' + userID)
  })
})

Інструкції

Щоб перевірити це, запустіть npm installінсталяцію ws. Потім, щоб запустити сервер чату, запустіть node server.js(або npm start) на одній вкладці терміналу. Потім на іншій вкладці терміналу запустіть wscat -c ws://localhost:5000/1, де 1знаходиться ідентифікатор користувача, що підключається. Потім, в третій вкладці терміналу, запустіть wscat -c ws://localhost:5000/2, а потім, щоб відправити повідомлення від користувача 2до 1, введіть ["1", "Hello, World!"].

Недоліки

Цей сервер чату дуже простий.

  • Наполегливість

    Він не зберігає повідомлення в базі даних, наприклад PostgreSQL. Отже, користувач, якому ви надсилаєте повідомлення, повинен бути підключений до сервера, щоб отримати його. В іншому випадку повідомлення втрачається.

  • Безпека

    Це невпевнено.

    • Якщо я знаю URL-адресу сервера та ідентифікатор користувача Аліси, то я можу видавати себе за Еліс, тобто підключатися до сервера як її, дозволяючи отримувати нові вхідні повідомлення та надсилати повідомлення від неї будь-якому користувачеві, ідентифікатор користувача якого я також знаю. Щоб зробити його більш безпечним, змініть сервер, щоб він приймав ваш маркер доступу (замість вашого ідентифікатора користувача) під час підключення. Потім сервер може отримати ваш ідентифікатор користувача з вашого маркера доступу та автентифікувати вас.

    • Я не впевнений , якщо він підтримує вразлива WebSocket ( wss://з'єднання) , так як я тільки перевірив його localhost, і я не знаю , як безпечно підключатися localhost.


Хто-небудь знає, як встановити локальне підключення WebSocket Secure ?
ma11hew28,

1
Те ж саме. Але якщо замінити частину 'upgradeReq ...' на 'ws.upgradeReq.headers [' sec-websocket-key ']', це працює. (через github.com/websockets/ws/issues/310 )
dirkk0

1
Найкращий приклад для запуску WS на практиці! Дякую
NeverEndingQueue

1
Чудовий приклад! Я шукав відповідь , як це в протягом останніх декількох днів
DIRTY DAVE

5

Для людей, які використовують wsверсію 3 або новішу. Якщо ви хочете використати відповідь, надану @ ma11hew28, просто змініть цей блок, як показано нижче.

webSocketServer.on('connection', function (webSocket) {
  var userID = parseInt(webSocket.upgradeReq.url.substr(1), 10)
webSocketServer.on('connection', function (webSocket, req) {
  var userID = parseInt(req.url.substr(1), 10)

ws пакет перемістив upgradeReq до запиту об'єкта, і ви можете перевірити наступне посилання для отримання детальної інформації.

Довідково: https://github.com/websockets/ws/issues/1114


1
ти врятував мене від безлічі клопоту.
Гіпотеза

1

Я хотів би поділитися тим, що я зробив. Сподіваюся, це не витратить ваш час.

Я створив таблицю бази даних, що містить ідентифікатор поля, IP, ім'я користувача, час входу та час виходу. Коли користувач входить в систему, час входу буде правильним unixtimestamp unix. І коли підключення починається в базі даних websocket, перевіряється найбільший час входу. Користувач увійде в систему.

І коли користувач виходить із системи, він зберігатиме точний час виходу. Користувач стане тим, хто залишив додаток.

Щоразу, коли з’являється нове повідомлення, Websocket ID та IP порівнюються та відображається відповідне ім’я користувача. Нижче наведено зразок коду ...

// when a client connects
function wsOnOpen($clientID) {
      global $Server;
      $ip = long2ip( $Server->wsClients[$clientID][6] );

      require_once('config.php');
      require_once CLASSES . 'class.db.php';
      require_once CLASSES . 'class.log.php';

      $db = new database();
      $loga = new log($db);

      //Getting the last login person time and username
      $conditions = "WHERE which = 'login' ORDER BY id DESC LIMIT 0, 1";
      $logs = $loga->get_logs($conditions);
      foreach($logs as $rows) {

              $destination = $rows["user"];
              $idh = md5("$rows[user]".md5($rows["time"]));

              if ( $clientID > $rows["what"]) {
                      $conditions = "ip = '$ip', clientID = '$clientID'  

                      WHERE logintime = '$rows[time]'";
                      $loga->update_log($conditions);
             }
      }
      ...//rest of the things
} 

0

цікавий пост (схожий на те, що я роблю). Ми створюємо API (на C #) для підключення дозаторів з WebSockets, для кожного дозатора ми створюємо ConcurrentD Dictionary, що зберігає WebSocket і DispenserId, що полегшує кожному Диспенсеру створення WebSocket і подальше його використання без проблем із потоком (викликаючи певні функції на WebSocket, як GetSettings або RequestTicket). Різниця для вашого прикладу полягає у використанні ConcurrentDictionary замість масиву для ізоляції кожного елемента (ніколи не намагався зробити це в javascript). З найкращими побажаннями,


Це може бути коментар, а не відповідь. Будь ласка, прочитайте правила.
dpapadopoulos
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.