Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/350.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 服务器发送了带有Jersey的事件:客户端删除后,EventOutput未关闭_Java_Rest_Jersey_Server Sent Events_Persistent Connection - Fatal编程技术网

Java 服务器发送了带有Jersey的事件:客户端删除后,EventOutput未关闭

Java 服务器发送了带有Jersey的事件:客户端删除后,EventOutput未关闭,java,rest,jersey,server-sent-events,persistent-connection,Java,Rest,Jersey,Server Sent Events,Persistent Connection,我正在使用jersey实现SSE场景 服务器使连接保持活动状态。并定期将数据推送到客户端 在我的场景中,有一个连接限制,只有一定数量的客户端可以同时订阅服务器 因此,当一个新客户机尝试订阅时,我会进行一次检查(EventOutput.isClosed),查看是否有旧连接不再处于活动状态,以便它们为新连接腾出空间 但是EventOutput.isClosed的结果始终为false,除非客户端显式调用EventSource的close。这意味着,如果一个客户端意外掉电(断电或互联网中断),它仍然占用

我正在使用jersey实现SSE场景

服务器使连接保持活动状态。并定期将数据推送到客户端

在我的场景中,有一个连接限制,只有一定数量的客户端可以同时订阅服务器

因此,当一个新客户机尝试订阅时,我会进行一次检查(EventOutput.isClosed),查看是否有旧连接不再处于活动状态,以便它们为新连接腾出空间

但是EventOutput.isClosed的结果始终为false,除非客户端显式调用EventSource的close。这意味着,如果一个客户端意外掉电(断电或互联网中断),它仍然占用连接,新客户端无法订阅


是否有解决方法?

希望提供以下方面的更新:

发生的情况是,客户端(js)上的eventSource从未进入readyState“1”,除非我们在添加新订阅后立即进行广播。即使在这种状态下,客户机也可以接收从服务器推送的数据。添加对简单“OK”消息广播的调用有助于将eventSource踢入readyState 1


从客户端关闭连接时;为了主动清理资源,仅仅关闭客户端的eventSource是没有帮助的。我们必须对服务器进行另一个ajax调用,以强制服务器进行广播。当强制广播时,jersey将清除不再活动的连接,并依次释放资源(连接处于关闭状态)。如果没有,连接将保持关闭状态,请等待下一次广播。

希望提供以下更新:

发生的情况是,客户端(js)上的eventSource从未进入readyState“1”,除非我们在添加新订阅后立即进行广播。即使在这种状态下,客户机也可以接收从服务器推送的数据。添加对简单“OK”消息广播的调用有助于将eventSource踢入readyState 1

从客户端关闭连接时;为了主动清理资源,仅仅关闭客户端的eventSource是没有帮助的。我们必须对服务器进行另一个ajax调用,以强制服务器进行广播。当强制广播时,jersey将清除不再活动的连接,并依次释放资源(连接处于关闭状态)。如果没有,一个连接将保持在关闭状态,等待下一次广播发生。

@翠鹏飞

因此,在我自己试图找到答案的旅行中,我偶然发现了一个存储库,它解释了如何优雅地清理断开连接的客户端的连接

将所有SSE EventOutput逻辑封装到服务/管理器中。在这种情况下,他们启动一个线程,检查客户端是否关闭了EventOutput。如果是这样,他们将正式关闭连接(EventOutput#close())。如果没有,则尝试写入流。如果抛出异常,则客户端在未关闭的情况下断开连接,并处理关闭异常。如果写入成功,则EventOutput将返回池,因为它仍然是活动连接

回购协议(和实际类别)可用。我还包括了下面没有导入的类,以防回购被删除

请注意,它们将此绑定到一个单例。商店应该是全球唯一的

公共类SseWriteManager{
私有最终ConcurrentHashMap connectionMap=新ConcurrentHashMap();
专用最终计划执行器服务消息执行器服务;
私有最终记录器Logger=LoggerFactory.getLogger(SseWriteManager.class);
公共写入管理器(){
messageExecutorService=Executors.newScheduledThreadPool(1);
messageExecutorService.scheduleWithFixedDelay(新messageProcessor(),0,5,TimeUnit.SECONDS);
}
public void addSseConnection(字符串id、EventOutput EventOutput){
info(“为id={}添加连接”,id);
connectionMap.put(id,eventOutput);
}
私有类messageProcessor实现可运行{
@凌驾
公开募捐{
试一试{
迭代器迭代器=connectionMap.entrySet().Iterator();
while(iterator.hasNext()){
布尔删除=假;
Map.Entry=iterator.next();
EventOutput EventOutput=entry.getValue();
if(eventOutput!=null){
if(eventOutput.isClosed()){
移除=真;
}否则{
试一试{
info(“写入id={}.”,entry.getKey());
eventOutput.write(新的OutboundEvent.Builder().name(“自定义消息”).data(String.class,“EOM”).build());
}捕获(例外情况除外){
logger.info(String.format(“写入id=%s失败”,entry.getKey()),ex);
移除=真;
}
}
}
如果(删除){
//我们正在删除eventOutput。如果尚未关闭,请关闭它。
如果(!eventOutput.isClosed()){
试一试{
eventOutput.close();
}捕获(例外情况除外){
//什么也不做。
}
}
iterator.remove();
}
}
}捕获(例外情况除外){
logger.error(“messageProcessor.run抛出异常。”,ex);
}
}
}
公共空间关闭(){
if(messageExecutorService!=null&&!messageExecutorService.isshutton()){
info(“SseWriteManager.shutdown:调用messageExecutorService.shutdown”);
messageExecutorService.shutdown();
}否则{
记录器。
public class SseWriteManager {

private final ConcurrentHashMap<String, EventOutput> connectionMap = new ConcurrentHashMap<>();

private final ScheduledExecutorService messageExecutorService;

private final Logger logger = LoggerFactory.getLogger(SseWriteManager.class);

public SseWriteManager() {
    messageExecutorService = Executors.newScheduledThreadPool(1);
    messageExecutorService.scheduleWithFixedDelay(new messageProcessor(), 0, 5, TimeUnit.SECONDS);
}

public void addSseConnection(String id, EventOutput eventOutput) {
    logger.info("adding connection for id={}.", id);
    connectionMap.put(id, eventOutput);
}

private class messageProcessor implements Runnable {
    @Override
    public void run() {
        try {
            Iterator<Map.Entry<String, EventOutput>> iterator = connectionMap.entrySet().iterator();
            while (iterator.hasNext()) {
                boolean remove = false;
                Map.Entry<String, EventOutput> entry = iterator.next();
                EventOutput eventOutput = entry.getValue();
                if (eventOutput != null) {
                    if (eventOutput.isClosed()) {
                        remove = true;
                    } else {
                        try {
                            logger.info("writing to id={}.", entry.getKey());
                            eventOutput.write(new OutboundEvent.Builder().name("custom-message").data(String.class, "EOM").build());
                        } catch (Exception ex) {
                            logger.info(String.format("write failed to id=%s.", entry.getKey()), ex);
                            remove = true;
                        }
                    }
                }
                if (remove) {
                    // we are removing the eventOutput. close it is if it not already closed.
                    if (!eventOutput.isClosed()) {
                        try {
                            eventOutput.close();
                        } catch (Exception ex) {
                            // do nothing.
                        }
                    }
                    iterator.remove();
                }
            }
        } catch (Exception ex) {
            logger.error("messageProcessor.run threw exception.", ex);
        }
    }
}

public void shutdown() {
    if (messageExecutorService != null && !messageExecutorService.isShutdown()) {
        logger.info("SseWriteManager.shutdown: calling messageExecutorService.shutdown.");
        messageExecutorService.shutdown();
    } else {
        logger.info("SseWriteManager.shutdown: messageExecutorService == null || messageExecutorService.isShutdown().");
    }

}}