如何使用Java EE 7 WebSockets实现向客户端推送?

10

我已经浏览了很多Web Socket示例、演示文稿,它们大多集中在一个相当简单的场景中,即客户端启动客户端-服务器通信。

我对另一种场景感兴趣,这种场景似乎同样实用:纯服务器向客户端推送。

我心目中的一个例子是更新网站上股票价值的应用程序。想象一下,有一个外部系统股票交易系统,它为每个订阅的股票价值变化发送一个JMS消息。

我想知道如何将这样的JMS事件转换为服务器推送,并从Java EE 7的角度高效、惯用地执行。

就我所理解的规范而言,我应该编写一个Web Socket端点。

@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通道和选择器可以做得更好,但这将需要某种“手工制作”的通道管理。虽然可行,但相当繁琐。

另一个解决方案是向其他系统发送ping以获取更新,但这也可能显得有些丑陋。而且,我也不确定 @OnOpen 方法是否应该以这种方式使用。

理想情况下,传入的JMS消息将触发对客户端的WebSocket推送。有什么好的方法来实现这样的功能呢?


1
https://blogs.oracle.com/brunoborges/entry/integrating_websockets_and_jms_with - Konstantin V. Salikhov
谢谢提供链接。看起来很不错(第7点中有实际的推送实现)。它是这样完成的:我们必须创建所有会话的集合(静态同步集合)。然后,如果我们想向客户端发送推送,我们就通过该集合并触发推送。这种方法有一些缺点,但没有什么是无法克服的。 - Piotr Kochański
我们的问题是密切相关的:https://dev59.com/xIXca4cB1Zd3GeqPHFo5#27045235 - Ihromant
上面的链接已经失效了。我实际上正在尝试做和原帖一样的事情,但是看起来如果没有使用某种计时器或在第一次连接时从服务器初始化推送消息(@OnOpen/@OnWebsocketConnect),这是不可能的。 - dre
4个回答

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);
        }
    }

}

使用getAsyncRemote()方法还是getBasicRemote()方法更好? - Renat Gatin

1
我这样做(不需要客户端请求):

@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());
    }
   }
}

1
将活动的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()方法。
下面是一个完整的示例,展示了如何使用HTML5 websocket API进行推送。 https://metamug.com/article/java-push-notification-with-websocket.php

-3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接