【问题标题】:How to send a message through web socket to a connected user?如何通过 Web 套接字向连接的用户发送消息?
【发布时间】:2017-08-05 21:45:15
【问题描述】:

我想通过网络套接字向特定用户发送消息。到目前为止,我可以像这样打开一个 Web 套接字并从客户端读取消息:

@ServerEndpoint(value = "/wsep")
public class WebSocketEndpoint {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketEndpoint.class);

    private Session session;

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        try {
            session.getBasicRemote().sendText("You are connected. Your ID is " + session.getId());
        } catch (Exception e) {
            LOGGER.error("Error on open web socket", e);
        }
    }

    @OnMessage
    public void onClientMessage(String message, Session session) {      
        LOGGER.info("Message from {} is: {}", session.getId(), message);
    }

    @OnClose
    public void onClose(Session session) {
        this.session = null;
        LOGGER.info("{} disconnected", session.getId());
    }
}

我有一个独立的服务,可以在目的地向用户创建消息。我的Message 类是一个简单的 POJO:

public class Message {
    private String fromUserName;
    private String toUserName;
    private String content;
    ...
}

当在我的MessageService 中创建新消息时,我想通知接收者是否已连接。我想我必须添加一个方法WebSocketEndpoint.onServerMessage

public void onServerMessage(Session session, Message message) {
    session.getBasicRemote().sendText(message.getContent());
}

但我不知道如何做这样行得通的事情。

【问题讨论】:

  • 看来你的实现是错误的。您应该使用静态映射来存储密钥为 id 的会话,或者可以是引用/标识符。 - 当客户端连接时,将其会话存储到映射。 - 当客户端关闭时,从地图中删除其会话。 - 创建新消息时,通过 id 查找并使用该会话发送消息。

标签: java websocket java-ee-7


【解决方案1】:

将为您的所有用户提供一个 ServerEndpoint 实例。因此,它应该存储所有客户端会话。正如 Vu.N 所建议的,一种方法是使用地图:

Map<String, Session> sessions = new ConcurrentHashMap<>();

public void onOpen(Session session) {
    String username = [...]
    sessions.put(username, session);
}

然后,将消息发送给用户就很容易了:

public void onServerMessage(Session session, Message message) {
    sessions.get(message.getToUserName())
            .getBasicRemote() // see also getAsyncRemote()
            .sendText(message.getContent());
}

现在最难的部分是获得username

在我过去的作品中,我通过 3 种方式做到了:

  1. 客户端连接到一个有一些“密钥”的 URL。此“密钥”将用于查找正确的用户名。 WebSocket 服务器端点是这样的:

    @ServerEndpoint(value="/wsep/{key}") // the URL will have an extra "key"
    public class WebSocketEndpoint {
    [...]
    @OnOpen
    public void onOpen(Session session, @PathParam("key") String key) {
        String username = getUserNameWithKey(key);
        sessions.add(username, session);
    }
    
  2. 客户端在第一条消息中发送一些信息。您只需忽略 @OnOpen 部分:

    @OnOpen
    public void onOpen(Session session) {
        LOGGER.info("Session open. ID: {}", session.getId());
    }
    
    @OnMessage
    public void onMessage(Session session, String message) {
        String username = getUserNameFromMessage(message);
        sessions.add(username, session);
    }
    
  3. 一些用户信息可以从 cookie、JWT 或其他东西中获取。您需要ServerEndpointConfig.Configurator 才能从请求中获取该信息。例如:

    public class CookieServerConfigurator extends ServerEndpointConfig.Configurator {
    
        @Override
        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
            Map<String,List<String>> headers = request.getHeaders();
            sec.getUserProperties().put("cookie", headers.get("cookie"));
        }
    }
    

    然后,服务器端点将指向该配置器:

    @ServerEndpoint(value = "/example2/", configurator = CookieServerConfigurator.class)
    public class WebSocketEndpoint {
    

    你可以得到这样的信息:

    @OnOpen
    public void onOpen(Session session, EndpointConfig endpointConfig) {
        String username = getUsername((List<String>)endpointConfig.getUserProperties().get("cookie"));
    

您可以在此处查看一些工作示例:https://github.com/matruskan/websocket-example

对于更复杂的系统,您还可以使用“标记系统”,而不是使用地图。然后,每个会话可以接收发送到其任何标签的消息,并且发送到标签的消息可以被定向到多个会话。

【讨论】:

  • 关于ServerEndpointConfig here有一个很好的答案
最近更新 更多