[英]Spring websocket send message from multiple threads
我正在为基于 spring 的项目之一使用 Spring WebSocket 服务器实现。 我遇到了一个错误,说The remote endpoint was in state [TEXT_PARTIAL_WRITING] which is invalid state
。 我发现问题是同时从不同的线程写入 websocket。
我如何临时修复它:考虑我已经实现了以下方法
void sendMessageToSession(WebsocketSession session,String message);
它将文本消息发送到 websocket session。 我无法使整个方法同步,因为多个线程可以为不同的 websocketSessions 和消息调用它。 我也不能把 session 放在同步块中(试过但没有用)
虽然,我这样解决了我的问题
synchronized(session.getId()){
//sending message;
}
我不再面临这个问题。 但在同步块中使用字符串似乎不是一个好习惯。 那么我还有什么其他解决方案? 发送异步消息的最佳方式是什么?
PS :建立连接后我已经使用了ConcurrentWebSocketSessionDecorator
,我正在使用更新的websocket。 没有帮助。
session = new ConcurrentWebSocketSessionDecorator(session, (int) StaticConfig.MAXIMUM_WS_ASYNC_SEND_TIMEOUT, StaticConfig.MAXIMUM_WS_BINARY_BUFFER_SIZE * 2);
注意我将我的 websocet 会话保存在 map 中,其中键是 session.getId,值是 session 本身。
与其他一些 websocket 实现不同,Spring websocket 引用在每条消息上似乎并不相同。 I saved sessions in a map by their ID, and on each message I check equality of the passed websocket with the websocket I already put on my map, its false.
通过在我的WebsocketSession
后面添加volatile
关键字,在我坚持会话的地方,我解决了这个问题。 我很高兴知道这是否也是一种不好的做法。 但我的想法是,当从多个线程写入 websocket 会话时,这些线程会丢失 websocket 的状态,因为它尚未更新,这就是抛出此异常的原因。
通过添加 volatile,我们确保 websocket 状态在另一个线程使用它之前已经更新,因此写入 websocket 可以按预期同步工作。
我创建了一个名为SessionData
的类,它包含 websocketSession 和我需要的有关会话的所有其他数据。
public class SessionData {
private volatile WebSocketSession websocketSession;
//...other
// getters and setters ...
}
我使用 SessionData 作为映射的值,其中会话 ID 是键
然后当从 SessionData 获取 websocketSession 并从不同的线程写入它时,volatile 帮助我更新了 websocketSession。
这里的一个关键注意事项是每次要向会话发送消息时都应该使用sessionData.getWebsocketSession.sendMessage(...)
。 你永远不应该使用直接对话,这意味着这样的代码是一个不好的做法:
WebSocketSession websocketSession = sessionData.getWebSocketSession();
websocketSession.sendMessage(...);
您永远不会知道这两行代码(在您的情况下可能超过 2 行)之间对 websocket 会话应用了哪些更改。
像这样的代码更好:
sessionData.getWebSocketSession().sendMessage(...);
也永远不要直接发布到在 Spring websocket MessageHandler
中传递给您的会话中。 否则,您可能会再次收到该错误。
这就是为什么在连接打开时将WebSocketSession
sessionId
映射到SessionData
好习惯。 您可以使用此存储库使用会话 ID 而不是直接使用会话来获取volatile session
。
ConcurrentWebSocketSessionDecorator在多线程中的作用就像一个魅力,它是为它而设计的。 您的地图实现可能有问题。
示例代码:
private final Map<String, SessionData> sessions = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception
{
// Use the following will crash :
//sessions.put(session.getId(), new SessionData(session));
// Use ConcurrentWebSocketSessionDecorator is safe :
sessions.put(session.getId(), new SessionData(new ConcurrentWebSocketSessionDecorator (session, 2000, 4096)));
super.afterConnectionEstablished(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception
{
sessions.remove(session.getId());
super.afterConnectionClosed(session, status);
}
public void send(WebSocketSession session, String msg) throws MessagingException {
try {
session.sendMessage (new TextMessage(msg));
} catch (IOException ex) {
throw new MessagingException(ex.getMessage());
}
}
要轻松测试多线程中的行为:
public void sendMT(WebSocketSession session, String msg) throws MessagingException{
for (int i=0; i<3; i++){
new Thread(){
@Override
public void run(){
send (session, msg);
}.start();
}
}
将 SubProtocolWebSocketHandler视为ConcurrentWebSocketSessionDecorator
的经过验证的用法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.