背景
笔者最近在开发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之外,也要定时发送心跳给服务器
本博客文章绝大多数为原创,少量为转载,代码经过测试验证,如果有疑问直接留言或者私信我。
创作文章不容易,转载文章必须注明文章出处;如果这篇文章对您有帮助,点击右侧打赏,支持一下吧。
创作文章不容易,转载文章必须注明文章出处;如果这篇文章对您有帮助,点击右侧打赏,支持一下吧。