Как реализовать отправку клиенту с помощью веб-сокетов Java EE 7?

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

Меня интересует другой сценарий, который кажется столь же практичным: чистое нажатие сервера на клиент.

Пример, который я имею в виду, — это приложение, которое обновляет стоимость акций на веб-сайте. Представьте себе, что существует внешняя система фондовой биржи, которая отправляет JMS-сообщение при каждом изменении стоимости подписки на акции.

Я хотел бы знать, как перевести такое входящее событие JMS в толчок сервера и эффективно и идиоматически с точки зрения Java EE 7.

Насколько я понимаю спецификацию, я должен написать конечную точку веб-сокета.

@ServerEndpoint("/demo")
public class WSEndpoint {
  private static final Logger LOG = Logger.getLogger(WSEndpoint.class);

  @OnMessage
  public void onMessage(String message, Session session) {
    LOG.info("Received : " + message + ", session:" + session.getId());
  }

  @OnOpen
  public void open(Session session) {
    LOG.info("Open session:" + session.getId());         
  }

  @OnClose
  public void close(Session session, CloseReason c) {
    log.info("Close session:" + session.getId());
  }
}

Все просто, когда я получаю сообщение от интерфейса, я могу делать все, что захочу в методе @OnMessage. Но в моем примере я не получу никакого сообщения от клиента, я получу событие от какой-то внешней системы.

Есть несколько подходов. Например, я могу создать поток в методе @OnOpen, как показано в этот блог. На практике этот подход может иметь недостаток, поскольку для каждого клиента мне нужно будет создать новый, потенциально долгоживущий поток.

Можно добиться большего успеха, используя каналы NIO с селекторами, но это потребует какого-то «ручного» управления каналами. Выполнимо, но довольно громоздко.

Другим решением было бы пропинговать какую-либо другую систему для получения обновлений, но опять же это было бы некрасиво. Кроме того, я также не уверен, предназначен ли метод @OnOpen для использования таким образом.

В идеале входящее сообщение JMS инициирует отправку клиенту веб-сокета. Любые идеи, как красиво реализовать что-то подобное?


person Piotr Kochański    schedule 16.10.2014    source источник
comment
blogs.oracle.com/brunoborges/entry/   -  person Konstantin V. Salikhov    schedule 17.10.2014
comment
Спасибо за ссылку. Выглядит неплохо (в пункте 7 есть реальная реализация push). Делается это следующим образом: мы должны создать набор всех сессий (статический синхронизированный набор). Затем, если мы хотим отправить push-уведомление клиенту, мы проходим через этот набор и запускаем push-уведомления. Есть несколько недостатков такого подхода, но нет ничего, что нельзя было бы преодолеть.   -  person Piotr Kochański    schedule 17.10.2014
comment
Наш вопрос тесно связан: stackoverflow.com/questions/27037570/   -  person Ihromant    schedule 21.11.2014
comment
Ссылка выше битая. На самом деле я пытаюсь сделать то же самое, что и OP, но начинает казаться, что это невозможно без использования какого-либо таймера или инициирования push-сообщения с сервера при первом подключении (@OnOpen/@OnWebsocketConnect).   -  person dre    schedule 17.02.2017


Ответы (4)


Возможно, это не самый элегантный способ, а просто для демонстрации идеи. Метод broadcast() отправит сообщение всем подключенным клиентам.

@ServerEndpoint("/echo")
public class ServerEndPoint {

    private static Set<Session> userSessions = Collections.newSetFromMap(new ConcurrentHashMap<Session, Boolean>());

    @OnOpen
    public void onOpen(Session userSession) {
        userSessions.add(userSession);
    }

    @OnClose
    public void onClose(Session userSession) {
        userSessions.remove(userSession);
    }

    @OnMessage
    public void onMessage(String message, Session userSession) {
        broadcast(message);
    }

    public static void broadcast(String msg) {
        for (Session session : userSessions) {
            session.getAsyncRemote().sendText(msg);
        }
    }

}
person Pavel Polushkin    schedule 23.12.2014
comment
лучше использовать метод getAsyncRemote() или getBasicRemote()? - person Renat Gatin; 03.05.2017

Я делаю это так (запрос клиента не требуется):

@ServerEndpoint("/hello")
public class HelloWebSocket {

   @OnOpen
   public void greetTheClient(Session session){
       try {
        session.getBasicRemote().sendText("Hello stranger");

    } catch (IOException ioe) {
        System.out.println(ioe.getMessage());
    }
   }
}
person Yakov Fain    schedule 24.12.2014

Сохраните активный sessionList в другом классе SessionManager.

List<Session> socketSessions = new ArrayList<>();

Добавить входящий сеанс в @OnOpen в список. Удалить сеанс из списка в @OnClose

@OnClose
public void close(Session session) {
  sessionManager.removeSession(session);
}

Чтобы отправить сообщение всем,

public void broadcast(String message){
    for(Session session: sessionList){
        session.getBasicRemote().sendText(message);
    }
}

Вы можете использовать метод sessionManager.broadcast() везде, где срабатывает событие.

Вот полный пример того, как веб-сокет можно использовать для отправки с помощью API веб-сокетов HTML5. https://metamug.com/article/java-push-notification-with-websocket.php

person Sorter    schedule 27.06.2018

Я реализовал решение, похожее на вашу проблему, с использованием компонента сокета omnifaces. Уведомления инициируются сообщениями JMS, а получателями могут быть определенные пользователи или все они. URL сообщения в блоге https://konstpan.wordpress.com/2018/03/25/push-notifications-in-a-jee-web-application-secured-by-keycloak/

person Panos Konstantinidis    schedule 29.03.2018