繁体   English   中英

Spring websocket 从多个线程发送消息

[英]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。


更新 (2020)

这里的一个关键注意事项是每次要向会话发送消息时都应该使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM