当使用websocket,向前端实时推送消息的时候,遇到如下异常:
2018-06-29 13:42:55 ERROR 988 --- [container-699] org.springframework.data.redis.listener.adapter.MessageListenerAdapter : [TxId : , SpanId : ] Listener execution failed
org.springframework.data.redis.listener.adapter.RedisListenerExecutionFailedException: Listener method 'crmpushReceive' threw exception; nested exception is java.lang.IllegalStateException: The remote endpoint was in state [TEXT_PARTIAL_WRITING] which is an invalid state for called method
at org.springframework.data.redis.listener.adapter.MessageListenerAdapter.invokeListenerMethod(MessageListenerAdapter.java:380) [spring-data-redis-1.8.1.RELEASE.jar:?]
at org.springframework.data.redis.listener.adapter.MessageListenerAdapter.onMessage(MessageListenerAdapter.java:310) [spring-data-redis-1.8.1.RELEASE.jar:?]
at org.springframework.data.redis.listener.RedisMessageListenerContainer.executeListener(RedisMessageListenerContainer.java:249) [spring-data-redis-1.8.1.RELEASE.jar:?]
at org.springframework.data.redis.listener.RedisMessageListenerContainer.processMessage(RedisMessageListenerContainer.java:239) [spring-data-redis-1.8.1.RELEASE.jar:?]
at org.springframework.data.redis.listener.RedisMessageListenerContainer$1.run(RedisMessageListenerContainer.java:967) [spring-data-redis-1.8.1.RELEASE.jar:?]
at java.lang.Thread.run(Thread.java:745) [?:1.8.0_65]
Caused by: java.lang.IllegalStateException: The remote endpoint was in state [TEXT_PARTIAL_WRITING] which is an invalid state for called method
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.checkState(WsRemoteEndpointImplBase.java:1224) ~[tomcat-embed-websocket-8.5.11.jar:8.5.11]
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.textPartialStart(WsRemoteEndpointImplBase.java:1182) ~[tomcat-embed-websocket-8.5.11.jar:8.5.11]
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendPartialString(WsRemoteEndpointImplBase.java:222) ~[tomcat-embed-websocket-8.5.11.jar:8.5.11]
at org.apache.tomcat.websocket.WsRemoteEndpointBasic.sendText(WsRemoteEndpointBasic.java:49) ~[tomcat-embed-websocket-8.5.11.jar:8.5.11]
at org.springframework.web.socket.adapter.standard.StandardWebSocketSession.sendTextMessage(StandardWebSocketSession.java:203) ~[spring-websocket-4.3.7.RELEASE.jar:4.3.7.RELEASE]
at org.springframework.web.socket.adapter.AbstractWebSocketSession.sendMessage(AbstractWebSocketSession.java:101) ~[spring-websocket-4.3.7.RELEASE.jar:4.3.7.RELEASE]
at com.msxf.afdi.biz.redis.RedisService.crmpushReceive(RedisService.java:105) ~[classes/:?]
at sun.reflect.GeneratedMethodAccessor185.invoke(Unknown Source) ~[?:?]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_65]
at java.lang.reflect.Method.invoke(Method.java:497) ~[?:1.8.0_65]
at org.springframework.data.redis.listener.adapter.MessageListenerAdapter$MethodInvoker.invoke(MessageListenerAdapter.java:145) ~[spring-data-redis-1.8.1.RELEASE.jar:?]
at org.springframework.data.redis.listener.adapter.MessageListenerAdapter.invokeListenerMethod(MessageListenerAdapter.java:374) ~[spring-data-redis-1.8.1.RELEASE.jar:?]
... 5 more
伪代码如下:
class A{
public static CopyOnWriteArraySet<WebSocketSession> CONNECTS = new CopyOnWriteArraySet<>();
public void handlerA(String message){
for (WebSocketSession session : CONNECTS ) {
session.sendMessage(new TextMessage(message));
}
}
public void handlerB(String message){
for (WebSocketSession session : CONNECTS ) {
session.sendMessage(new TextMessage(message));
}
}
}
其实原因就是: handlerA和handlerB两个方法有可能同时执行,当A或者B方法遍历到某一个session并且调用sendMessage发送消息的时候,另外一个方法也正好也在使用相同的session发送另外一个消息(同一个session消息发送冲突了,也就是说同一个时刻,多个线程向一个socket写数据冲突了),就会报上面的异常。
解决方法其实很简单,就是在发送消息的时候加上一把锁,(保证一个session在某个时刻不会被调用多次):
class A{
public static CopyOnWriteArraySet<WebSocketSession> CONNECTS = new CopyOnWriteArraySet<>();
public void handlerA(String message){
for (WebSocketSession session : CONNECTS ) {
synchronized (session) {
session.sendMessage(new TextMessage(message));
}
}
}
public void handlerB(String message){
for (WebSocketSession session : CONNECTS ) {
synchronized (session) {
session.sendMessage(new TextMessage(message));
}
}
}
}
注意:如果服务器是配置了https的话, websocket需要用wss协议