如何获取现有的websocket实例

Ary*_*kat 13 java singleton instance websocket java-ee-7

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

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

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

那么,请你建议我一个方法,我可以得到一个现有的websocket端点实例,因为我无法创建它作为新的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;
}
Run Code Online (Sandbox Code Playgroud)

提前致谢.

Ian*_*ans 21

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

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) { ... }
  } 
} 
Run Code Online (Sandbox Code Playgroud)

但在您的情况下,您可能希望使用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) { ... }
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

...
@Inject
Event<ArticleEvent> newArticleEvent;

public void publishArticle(Article article) {
  ...
  newArticleEvent.fire(new ArticleEvent(article));
  ...
}
Run Code Online (Sandbox Code Playgroud)

请参阅有关WebSocketsCDI Events的Java EE 7 Tutorial章节.

编辑:修改了@Observer将事件用作参数的方法.

编辑2:在@gcvt中,在synchronizedArticle中包装循环.

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

  • 在`broadcastArticle`中你应该在for循环之前做`synchronized(sessions)`. (2认同)

Pav*_*cek 9

实际上,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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

您需要将其注册到端点:

@ServerEndpoint(value = "/echoAnnotated", configurator = MyServerConfigurator.class)
public static class MyEndpointAnnotated {

    @OnMessage
    public String onMessage(String message) {

        assertEquals(MyServerConfigurator.testEndpoint1, this);

        return message;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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));
    }
Run Code Online (Sandbox Code Playgroud)

当然,如果您有一个用于所有端点的配置程序(如所示的片段中的丑陋ifs),或者您将为每个端点创建单独的配置程序,则由您决定.

请不要复制现有的代码 - 这只是Tyrus测试的一部分,它确实违反了一些基本的OOM范例.

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