【问题标题】:How to get an existing websocket instance如何获取现有的 websocket 实例
【发布时间】:2013-08-31 03:42:33
【问题描述】:

我正在开发一个应用程序,该应用程序使用 Websockets (Java EE 7) 向所有连接的客户端异步发送消息。每当创建新文章(我的应用程序中的参与模式)时,服务器(Websocket 端点)应该发送这些消息。

每次与 websocket 端点建立连接时,我都会将相应的会话添加到一个列表中,我可以在外部访问该列表。

但我遇到的问题是,当我访问所有客户端从外部(任何其他业务类)连接到的这个创建的 websocket 端点时,我已经获得了现有实例(如单例)。

那么,您能否建议我一种方法,我可以获得 websocket 端点的现有实例,因为我无法将它创建为 new MyWebsocketEndPoint() 因为每当来自请求时,它将由 websocket 内部机制创建收到一个客户。

对于参考:

private static WebSocketEndPoint INSTANCE = null;

public static WebSocketEndPoint getInstance() {
if(INSTANCE == null) {
// Instead of creating a new instance, I need an existing one
    INSTANCE = new WebSocketEndPoint ();
}
        return INSTANCE;
}

提前致谢。

【问题讨论】:

    标签: java websocket singleton instance java-ee-7


    【解决方案1】:

    容器会为每个客户端连接创建一个单独的端点实例,因此您无法执行您想要执行的操作。但我认为您要做的是在事件发生时向所有活动的客户端连接发送消息,这相当简单。

    javax.websocket.Session 类具有 getBasicRemote 方法来检索代表与该会话关联的端点的 RemoteEndpoint.Basic 实例。

    您可以通过调用Session.getOpenSessions() 检索所有打开的会话,然后遍历它们。该循环将向每个客户端连接发送一条消息。这是一个简单的例子:

    @ServerEndpoint("/myendpoint")
    public class MyEndpoint {
      @OnMessage
      public void onMessage(Session session, String message) {
        try {  
          for (Session s : session.getOpenSessions()) {
            if (s.isOpen()) {
              s.getBasicRemote().sendText(message);
            }
        } catch (IOException ex) { ... }
      } 
    } 
    

    但在您的情况下,您可能希望使用 CDI 事件来触发对所有客户端的更新。在这种情况下,您将创建一个 CDI 事件,您的 Websocket 端点类中的方法会观察到该事件:

    @ServerEndpoint("/myendpoint")
    public class MyEndpoint {
      // EJB that fires an event when a new article appears
      @EJB
      ArticleBean articleBean;
      // a collection containing all the sessions
      private static final Set<Session> sessions = 
              Collections.synchronizedSet(new HashSet<Session>());
    
      @OnOpen
      public void onOpen(final Session session) {
        // add the new session to the set
        sessions.add(session);
        ...
      }
    
      @OnClose
      public void onClose(final Session session) {
        // remove the session from the set
        sessions.remove(session);
      }
    
      public void broadcastArticle(@Observes @NewArticleEvent ArticleEvent articleEvent) {
        synchronized(sessions) {
          for (Session s : sessions) {
            if (s.isOpen()) {
              try {
                // send the article summary to all the connected clients
                s.getBasicRemote().sendText("New article up:" + articleEvent.getArticle().getSummary());
              } catch (IOException ex) { ... }
            }
          }
        }
      }
    }
    

    上例中的 EJB 会执行以下操作:

    ...
    @Inject
    Event<ArticleEvent> newArticleEvent;
    
    public void publishArticle(Article article) {
      ...
      newArticleEvent.fire(new ArticleEvent(article));
      ...
    }
    

    请参阅 WebSocketsCDI Events 上的 Java EE 7 教程章节。

    编辑:修改@Observer 方法以使用事件作为参数。

    编辑 2:根据@gcvt,将循环包装在 broadcastArticle 中同步。

    编辑 3:更新了 Java EE 7 教程的链接。干得好,甲骨文。嘘。

    【讨论】:

    • 这当然是一个很好的答案。我已经在使用 RemoteEndpoint.Async,你在代码 sn-p 1 中提到的所有内容。但我的问题是,我不必将创建的文章发送给所有连接的客户端。我已经做了一些过滤 b4 发送。而且我们的框架不使用 EJB,我们有一个单独的业务类(比如 ArticleHandler)frm,我可以在其中知道创建了一篇文章。如果我想在那里访问 session.getOpenSessions() frm,我需要已经存储在缓存中某处的会话对象。帮我到达那里。
    • 上面的例子只是一个通用的架构。您不必使用 EJB。在您的情况下,ArticleHandler 将注入事件并触发它。至于访问会话,将Set&lt;Session&gt; 集合移动到单例,并从端点类调用单例以将会话存储在那里。然后单例将有一个getSessions() 方法来检索当前会话。
    • broadcastArticle 你应该在 for 循环之前做 synchronized(sessions)
    • Java EE 7 链接为 404。
    • @DonRhummy 是的,假设您的容器支持 CDI,您应该能够做到这一点。
    【解决方案2】:

    实际上,WebSocket API 提供了一种控制端点实例化的方法。见https://tyrus.java.net/apidocs/1.2.1/javax/websocket/server/ServerEndpointConfig.Configurator.html

    简单样本(取自Tyrus - WebSocket RI测试):

        public static class MyServerConfigurator extends ServerEndpointConfig.Configurator {
    
            public static final MyEndpointAnnotated testEndpoint1 = new MyEndpointAnnotated();
            public static final MyEndpointProgrammatic testEndpoint2 = new MyEndpointProgrammatic();
    
            @Override
            public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
                if (endpointClass.equals(MyEndpointAnnotated.class)) {
                    return (T) testEndpoint1;
                } else if (endpointClass.equals(MyEndpointProgrammatic.class)) {
                    return (T) testEndpoint2;
                }
    
                throw new InstantiationException();
            }
        }
    

    您需要将其注册到端点:

    @ServerEndpoint(value = "/echoAnnotated", configurator = MyServerConfigurator.class)
    public static class MyEndpointAnnotated {
    
        @OnMessage
        public String onMessage(String message) {
    
            assertEquals(MyServerConfigurator.testEndpoint1, this);
    
            return message;
        }
    }
    

    或者您也可以将它与编程端点一起使用:

    public static class MyApplication implements ServerApplicationConfig {
        @Override
        public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> endpointClasses) {
            return new HashSet<ServerEndpointConfig>
              (Arrays.asList(ServerEndpointConfig.Builder
                .create(MyEndpointProgrammatic.class, "/echoProgrammatic")
                .configurator(new MyServerConfigurator())
                .build()));
        }
    
        @Override
        public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) {
            return new HashSet<Class<?>>(Arrays.asList(MyEndpointAnnotated.class));
        }
    

    当然,如果您将一个配置器用于所有端点(丑陋的 ifs,如呈现的 sn-p),或者您是否将为每个端点创建单独的配置器,这取决于您。

    请不要照原样复制提供的代码 - 这只是 Tyrus 测试的一部分,它确实违反了一些基本的 OOM 范例。

    完整测试请参见https://github.com/tyrus-project/tyrus/blob/1.2.1/tests/e2e/src/test/java/org/glassfish/tyrus/test/e2e/GetEndpointInstanceTest.java

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-13
      • 2023-01-30
      • 2020-08-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多