How to get an existing websocket instance

前端 未结 2 758
礼貌的吻别
礼貌的吻别 2020-12-24 03:12

I\'m working on an application that uses Websockets (Java EE 7) to send messages to all the connected clients asynchronously. The server (Websocket endpoint) should send the

相关标签:
2条回答
  • 2020-12-24 04:01

    Actually, WebSocket API provides a way how you can control endpoint instantiation. See https://tyrus.java.net/apidocs/1.2.1/javax/websocket/server/ServerEndpointConfig.Configurator.html

    simple sample (taken from Tyrus - WebSocket RI test):

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

    You need to register this to an endpoint:

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

    or you can use it with programmatic endpoints as well:

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

    Of course it is up to you if you will have one configurator used for all endpoints (ugly ifs as in presented snippet) or if you'll create separate configurator for each endpoint.

    Please do not copy presented code as it is - this is only part of Tyrus tests and it does violate some of the basic OOM paradigms.

    See https://github.com/tyrus-project/tyrus/blob/1.2.1/tests/e2e/src/test/java/org/glassfish/tyrus/test/e2e/GetEndpointInstanceTest.java for complete test.

    0 讨论(0)
  • 2020-12-24 04:09

    The container creates a separate instance of the endpoint for every client connection, so you can't do what you're trying to do. But I think what you're trying to do is send a message to all the active client connections when an event occurs, which is fairly straightforward.

    The javax.websocket.Session class has the getBasicRemote method to retrieve a RemoteEndpoint.Basic instance that represents the endpoint associated with that session.

    You can retrieve all the open sessions by calling Session.getOpenSessions(), then iterate through them. The loop will send each client connection a message. Here's a simple example:

    @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) { ... }
      } 
    } 
    

    But in your case, you probably want to use CDI events to trigger the update to all the clients. In that case, you'd create a CDI event that a method in your Websocket endpoint class observes:

    @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) { ... }
            }
          }
        }
      }
    }
    

    The EJB in the above example would do something like:

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

    See the Java EE 7 Tutorial chapters on WebSockets and CDI Events.

    Edit: Modified the @Observer method to use an event as a parameter.

    Edit 2: wrapped the loop in broadcastArticle in synchronized, per @gcvt.

    Edit 3: Updated links to Java EE 7 Tutorial. Nice job, Oracle. Sheesh.

    0 讨论(0)
提交回复
热议问题