Java 如何使用WebSocket定期发送消息?

Java 如何使用WebSocket定期发送消息?,java,multithreading,jakarta-ee,websocket,Java,Multithreading,Jakarta Ee,Websocket,我在Tomcat上使用WebSocket(实际实现是Tyrus,JSR356的参考实现)。当我必须处理客户端消息并对其作出响应时,它非常有效。但是,我想为我的几个客户端控件实现一个推送解决方案。实际上,我需要两种类型的解决方案: 以特定的时间间隔推出数据 发出系统消息时,将其推出 对于第一个,我认为这是一个解决方案,我已经有了一个或多或少的工作示例,但是我在清理方面有问题。对于第二个,我想我需要一个线程,它将触发WebSocket端点中的一个方法,但我也不知道如何干净地完成这项工作。所谓干净

我在Tomcat上使用WebSocket(实际实现是Tyrus,JSR356的参考实现)。当我必须处理客户端消息并对其作出响应时,它非常有效。但是,我想为我的几个客户端控件实现一个推送解决方案。实际上,我需要两种类型的解决方案:

  • 以特定的时间间隔推出数据
  • 发出系统消息时,将其推出
对于第一个,我认为这是一个解决方案,我已经有了一个或多或少的工作示例,但是我在清理方面有问题。对于第二个,我想我需要一个线程,它将触发WebSocket端点中的一个方法,但我也不知道如何干净地完成这项工作。所谓干净,我的意思是,只有当有连接到端点的会话时,我才希望有正在运行的线程

总结一下我的问题:如何使用JavaEEWebSocket API正确地实现推送消息解决方案

注:我更喜欢“纯”的解决方案,但春天也不是不受欢迎的


当前代码框架 对于第一个问题,我当前的解决方案是这样的:

@ServerEndpoint(...)
public class MyEndPoint {
    // own class, abstracting away session handling
    private static SessionHandler sessionHandler = new SessionHandler();
    private static ScheduledExecutorService timer =
            Executors.newSingleThreadScheduledExecutor();
    private static boolean timerStarted = false;

    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        sessionHandler.addSession(session);
        if (!timerStarted) {
            timer.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    sessionHandler.sendToAllSession("foo");
                }
            }, 0, 3, TimeUnit.SECONDS);
            timerStarted = true;
        }
    }

    @OnClose
    public void onClose(Session session) {
        sessionHandler.removeSession(session);
        if (0 == sessionHandler.countSessions()) {
            // TODO: cleanup thread properly
            timer.shutdown();
            try {
                while (!timer.awaitTermination(10, TimeUnit.SECONDS));
            } catch (InterruptedException e) {
                log.debug("Timer terminated.");
            }
            timerStarted = false;
        }
    }
}

这或多或少能起作用,但在重新加载几页后,它会随着
RejectedExecutionException
而消失,我不太确定如何处理这种情况。

不幸的是,关闭()后您无法使用任何ExecutorService

所以在OnClose()方法之后,下一个OnOpen()方法将崩溃

只需一些代码即可演示:

public class TestThread {

    public static void main(String[] args) {

        final ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor();
        boolean timerStarted = false;
        //OnOpen - 1;  - OK
        if (!timerStarted) {
            timer.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("foo");
                }
            }, 0, 3, TimeUnit.SECONDS);
            timerStarted = true;
        }

        //OnOpen - 2;  - OK
        if (!timerStarted) {
            timer.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("foo");
                }
            }, 0, 3, TimeUnit.SECONDS);
            timerStarted = true;
        }

        //OnClose - 1  - OK
        timer.shutdown();
        timerStarted = false;

        //OnOpen - 2;  - NOT OK, because after stop you can't use timer,  RejectedExecutionException will thrown
        if (!timerStarted) {
            // will crash at this invocke
            timer.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("foo");
                }
            }, 0, 3, TimeUnit.SECONDS);
            timerStarted = true;
        }
    }
}
您也可以尝试将类用作web侦听器 并在服务器启动和销毁时执行的方法中创建计时器

@WebListener
@ServerEndpoint(...)  
public class MyEndPoint implements ServletContextListener{

    final ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor();

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        timer.scheduleWithFixedDelay(...)
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        timer.shutdown();
    }

    ...
}

我喜欢这个主意。不过,我很可能会采用不同的方法。端点将只管理会话,一个不同的定期服务将调用会话的send方法。