Socket.io получает сообщения до того, как браузер перейдет на новую страницу

У меня возникла проблема с получением Socket.io сообщений непосредственно перед переходом по странице - обычно, когда сообщение является прямым результатом какого-либо действия на стороне сервера, вызванного навигацией.

То, что я сейчас вижу, выглядит так:

  1. Socket.io подключается
  2. Пользователь инициирует навигацию по странице (отправляет форму, обновляет и т. д.)
  3. Логика на стороне сервера отправляет запрос на сервер socket.io, который немедленно отправляет событие все еще подключенному клиенту.
  4. Клиент получает и подтверждает запрос (я почти уверен, что есть какое-то подтверждение сообщений, встроенных в socket.io, поправьте меня, если я ошибаюсь), и отобразит уведомление пользователю, но затем...
  5. Соединение socket.io закрывается на исходной странице
  6. Новая страница загружается и отображается
  7. Socket.io открывает новое соединение, но новых сообщений нет, потому что последнее было получено и подтверждено.

На самом деле это не ошибка, поскольку я не думаю, что разумно ожидать, что Socket.io закроет соединение до того, как произойдет навигация. Тем не менее, я не совсем уверен, как лучше всего справиться с этим. В настоящее время я держу одно соединение открытым для каждого клиента за раз и закрываю другое, когда подключается новый. Однако в данном случае этого не происходит, поскольку первый закрылся до того, как подключился второй. Я также мог бы вести список всех клиентов, но это тоже не решило бы эту проблему, потому что сообщение все равно было бы получено при первом подключении.

Может ли кто-нибудь предложить решение этой проблемы, которое гарантирует, что пользователь всегда будет видеть уведомление о сообщении?


person robbles    schedule 24.08.2012    source источник


Ответы (2)


Socket.io отслеживает логические соединения с помощью собственных идентификаторов сеансов. Если вы посмотрите на консоль при подключении клиента, вы увидите идентификаторы:

info  - handshake authorized Q9syoIK47JI7dACYpxiA

Важно понимать, что эти идентификаторы для каждой страницы и полностью отделены от сеансов HTTP. Клиентская библиотека Socket.io просто хранит свой идентификатор сеанса в переменной JavaScript. Поэтому при навигации идентификатор явно теряется.

Итак, при навигации происходит следующее:

  1. Пользователь находится на странице, подключенной к сеансу sio 1.
  2. Начинаем переход на новую страницу. На window.onbeforeunload Socket.io инициирует синхронный запрос XHR, чтобы сообщить серверу об отключении. В случае успеха сеанс (1) немедленно завершается; в противном случае время сеанса в конечном итоге истечет.
  3. Загружается новая страница. Он подключится к серверу Socket.io и получит новый идентификатор сеанса, 2.
  4. Все, что вы отправите в сеанс 1, очевидно, не будет доставлено, так как наш клиент теперь подключен к сеансу 2.

С базовой функциональностью Socket.io невозможно отличить пользователя, который перемещается между страницами, и нового пользователя. В любом случае пользователь подключится к новому сеансу Socket.io.

Не зная точно, как работает ваше приложение или чего вы пытаетесь достичь, трудно дать однозначную рекомендацию по решению вашей проблемы.

Скорее всего, вам нужно иметь возможность связать сеанс Socket.io с пользовательским HTTP-сеанс. Вы можете хранить уведомления в очереди в сеансе пользователя и удалять их при отображении. Есть два способа сделать это:

  • Поскольку вы выполняете полную загрузку страницы, вы можете отправлять уведомления в очереди непосредственно вниз с самой страницы. Удалите очередь после успешного рендеринга.
  • При новом соединении Socket.io отправьте через сокет непрочитанные уведомления. Дать .emit функцию обратного вызова это подтверждение доставки, которое предоставляет вам Socket.io . Когда доставка подтверждена, вы можете удалить очередь уведомлений из HTTP-сеанса пользователя.
person josh3736    schedule 24.08.2012
comment
Прямо сейчас я реализую свое собственное управление сеансами на уровне пользователя. Я знаю о разных идентификаторах сеансов для каждого соединения, и, как я уже упоминал выше, я отключаю все старые соединения, когда новое соединение аутентифицируется. Похоже, ваше предложение с обратным вызовом на .emit будет проще всего реализовать, хотя я немного беспокоюсь, что обратный вызов будет вызван до перезагрузки страницы. Возможно ли это с задержкой? - person robbles; 25.08.2012
comment
Посмотрите на это так: если вы отправляете уведомление в результате отправки формы, в тот момент, когда вы хотите отправить уведомление, старое соединение сокета уже исчезло, и новое соединение не будет до тех пор, пока ответ был отправлен в браузер. Таким образом, вам нужен метод постановки сообщений в очередь на уровень выше Socket.io; сеанс пользователя является естественным местом для этого. - person josh3736; 25.08.2012

Самое простое решение: используйте событие onbeforeunload для отключения socket.io.

Я проверил с помощью отладчика HTTP, что это событие срабатывает до того, как браузер отправит запрос (по крайней мере, в Chrome), поэтому, если socket.io здесь отключен, он не получит никаких сообщений, предназначенных для следующей страницы.

window.onbeforeunload = function() {
    socket.disconnect();
};

Я не проверял это в нескольких браузерах, поэтому это может быть не идеальное решение, но на данный момент оно решает проблему.

person robbles    schedule 27.08.2012