背景

笔者最近在开发Websocket相关的消息推送服务,使用了JSR356规范,由于需要维持会话。于是分别使用了以下类

//客户端缓存<dispatchNo,ebSocketServer>
    private static final ConcurrentHashMap<String, Set<WebSocketServer>> clientRegistry = new ConcurrentHashMap<>();
 //客户端缓存<dispatchNo,ebSocketServer>
    private static final ConcurrentMap<String, Set<WebSocketServer>> clientRegistry = new ConcurrentSkipListMap<>();

 

笔者在维护失效的代码写了以下代码

/**
 * 连接关闭调用的方法
 */
@OnClose
public void onClose() {
    for(final Iterator<Map.Entry<String, Set<WebSocketServer>>> iterator = clientRegistry.entrySet().iterator();
                iterator.hasNext();)
    {
        final Map.Entry<String, Set<WebSocketServer>> entry = iterator.next();
        final String dispatchNo = entry.getKey();
        final Set<WebSocketServer> clients = entry.getValue();

        for(final Iterator<WebSocketServer> clients = entry.getValue().iterator();
            clients.hasNext();) {

            final WebSocketServer client = clients.next();
            if (Objects.equals(client, this)) {
                // 断开连接情况下,更新主板占用情况为释放
                log.info("session:{}取消订阅工序派工单{}的产生数据", session.getId(), dispatchNo);
                clients.remove();
            } else {

                final Session session = client.session;
                final boolean isAlive = session.isOpen();
                if (!isAlive) {
                    clients.remove();
                }
            }
        }
    }
}

最终在调用clients.remove();时候报错

java.lang.UnsupportedOperationException: null
    at java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1178) ~[?:1.8.0_202]
    at com.xxxx.mes.websocket.WebSocketServer.onClose(WebSocketServer.java:192) ~[classes/:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_202]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_202]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_202]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_202]
    at io.undertow.websockets.jsr.annotated.BoundMethod.invoke(BoundMethod.java:87) ~[undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.annotated.AnnotatedEndpoint$4.run(AnnotatedEndpoint.java:201) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.ServerWebSocketContainer$1.call(ServerWebSocketContainer.java:170) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.ServerWebSocketContainer$1.call(ServerWebSocketContainer.java:167) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) [undertow-servlet-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.ServerWebSocketContainer.invokeEndpointMethod(ServerWebSocketContainer.java:610) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.ServerWebSocketContainer.invokeEndpointMethod(ServerWebSocketContainer.java:600) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.annotated.AnnotatedEndpoint.onClose(AnnotatedEndpoint.java:196) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.UndertowSession.closeInternal(UndertowSession.java:235) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.UndertowSession.close(UndertowSession.java:194) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.ServerWebSocketContainer.doClose(ServerWebSocketContainer.java:981) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.ServerWebSocketContainer.close(ServerWebSocketContainer.java:839) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.ServerWebSocketContainer.close(ServerWebSocketContainer.java:848) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.websockets.jsr.Bootstrap$WebSocketListener.contextDestroyed(Bootstrap.java:133) [undertow-websockets-jsr-2.0.32.Final.jar:2.0.32.Final]
    at io.undertow.servlet.core.ApplicationListeners.contextDestroyed(ApplicationListeners.java:202) [undertow-servlet-2.0.32.Final.jar:2.0.32.Final]

我明明用Set接口为啥是ArrayList接口,很奇怪,可能内部实现使用arraylist。不让删除的原因可能是并发 条件下时间复杂度比较高。(比较忙,没时间看代码解释)

解决方案如下

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {

        for(final Iterator<Map.Entry<String, Set<WebSocketServer>>> iterator = clientRegistry.entrySet().iterator();
            iterator.hasNext();)
        {
            final Map.Entry<String, Set<WebSocketServer>> entry = iterator.next();
            final String dispatchNo = entry.getKey();
            final Set<WebSocketServer> clients = entry.getValue();

            for(WebSocketServer client: clients){
                if (Objects.equals(client, this)) {
                    // 断开连接情况下,更新主板占用情况为释放
                    log.info("session:{}取消订阅工序派工单{}的产生数据", session.getId(), dispatchNo);
                    clients.remove(client);
                } else {

                    final Session session = client.session;
                    final String sessionId = session.getId();
                    final boolean isAlive = session.isOpen();
                    if (!isAlive) {
                        clients.remove(client);
                        log.info("客户端会话@id:{}失效,已经踢出缓存",sessionId);
                    }
                }
            }
        }

        final int clientCount = clientRegistry.values().stream().mapToInt(item -> item.size()).sum();

        if(clientCount > 0) {
            decreaseOnlineCount();
        }
    }

 

websocket前后端长链接参考

https://blog.csdn.net/qq_32330135/article/details/85282050 

websocket客户端,在onmessage之外,也要定时发送心跳给服务器

posted on 2022-03-19 16:43  你不知道的浪漫  阅读(606)  评论(0编辑  收藏  举报