【发布时间】:2015-04-17 14:09:36
【问题描述】:
我已经搜索了很多,但无法找到:Spring websocket stomp 服务器是否可以根据 sessionId(或实际上基于任何东西)断开客户端?
在我看来,一旦客户端连接到服务器,就没有任何东西可以让服务器断开客户端的连接。
【问题讨论】:
标签: spring spring-boot stomp spring-websocket
我已经搜索了很多,但无法找到:Spring websocket stomp 服务器是否可以根据 sessionId(或实际上基于任何东西)断开客户端?
在我看来,一旦客户端连接到服务器,就没有任何东西可以让服务器断开客户端的连接。
【问题讨论】:
标签: spring spring-boot stomp spring-websocket
实际上使用一些变通方法可以实现您想要的。 为此,您应该这样做:
我创建了示例 spring-boot 项目来展示我们如何断开客户端会话与服务器端的连接: https://github.com/isaranchuk/spring-websocket-disconnect
【讨论】:
您还可以通过实现自定义WebSocketHandlerDecorator 来断开会话:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig<S extends ExpiringSession> extends AbstractSessionWebSocketMessageBrokerConfigurer<S> {
@Override
public void configureWebSocketTransport(final WebSocketTransportRegistration registration) {
registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
@Override
public WebSocketHandler decorate(final WebSocketHandler handler) {
return new WebSocketHandlerDecorator(handler) {
@Override
public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
session.close(CloseStatus.NOT_ACCEPTABLE);
super.afterConnectionEstablished(session);
}
};
}
});
super.configureWebSocketTransport(registration);
}
@Override
protected void configureStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/home")
.setHandshakeHandler(new DefaultHandshakeHandler(
new UndertowRequestUpgradeStrategy() // If you use undertow
// new JettyRequestUpgradeStrategy()
// new TomcatRequestUpgradeStrategy()
))
.withSockJS();
}
}
【讨论】:
afterConnectionEstablished 时消息仍未处理怎么办。这种情况可能吗?
据我所知,API 没有提供您正在寻找的内容,在服务器端您只能检测断开连接事件。如果您想断开某个客户端的连接,我认为您必须采取一些解决方法,例如这个:
解决方法有点麻烦,但会奏效。
【讨论】:
SubProtocolWebSocketHandler.java的afterConnectionEstablished()方法的源码可以阅读:// WebSocketHandlerDecorator could close the session
我依靠@Dániel Kis 的想法并实现了websocket 会话管理,其关键点是将经过身份验证的用户的websocket 会话存储在类似Singleton 的对象中。
// WebSocketConfig.java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
@Override
public WebSocketHandler decorate(final WebSocketHandler handler) {
return new WebSocketHandlerDecorator(handler) {
@Override
public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
// We will store current user's session into WebsocketSessionHolder after connection is established
String username = session.getPrincipal().getName();
WebsocketSessionHolder.addSession(username, session);
super.afterConnectionEstablished(session);
}
};
}
});
}
}
存储 websocket 用户会话的类 WebsocketSessionHolder。我使用“同步”块来保证线程安全。实际上,这些块并不是昂贵的操作,因为每个方法(addSession 和 closeSessions)都不经常使用(在建立和终止连接时)。此处无需使用 ConcurrentHashMap 或 SynchronizedMap,因为我们在这些方法中对列表执行了一堆操作。
// WebsocketSessionHolder.java
public class WebsocketSessionHolder {
static {
sessions = new HashMap<>();
}
// key - username, value - List of user's sessions
private static Map<String, List<WebSocketSession>> sessions;
public static void addSession(String username, WebSocketSession session)
{
synchronized (sessions) {
var userSessions = sessions.get(username);
if (userSessions == null)
userSessions = new ArrayList<WebSocketSession>();
userSessions.add(session);
sessions.put(username, userSessions);
}
}
public static void closeSessions(String username) throws IOException
{
synchronized (sessions) {
var userSessions = sessions.get(username);
if (userSessions != null)
{
for(var session : userSessions) {
// I use POLICY_VIOLATION to indicate reason of disconnecting for a client
session.close(CloseStatus.POLICY_VIOLATION);
}
sessions.remove(username);
}
}
}
}
最后一步 - 终止(断开)指定的用户 websocket 会话(示例中为“ADMIN”),例如在某些控制器中
//PageController.java
@Controller
public class PageController {
@GetMapping("/kill-sessions")
public void killSessions() throws Exception {
WebsocketSessionHolder.closeSessions("ADMIN");
}
}
【讨论】:
如果是 xml 配置,您可以在 <websocket:message-broker> 的 <websocket:transport> 中使用 <websocket:decorator-factories>。
创建自定义 WebSocketHandlerDecorator 和 WebSocketHandlerDecoratorFactory 实现 decorate 方法。
【讨论】:
这可能看起来很简短,但我不确定在您的情况下实现会是什么样子。但是,我认为在某些情况下需要这种解决方法/解决方案:
@Bean
public ServletServerContainerFactoryBean websocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxSessionIdleTimeout(MAX_SESSION_IDLE_TIMEOUT);
return container;
}
当然,如果您想立即断开连接,这似乎不是一个合适的解决方案。但是,如果您只是想减少活动连接的数量,那么 ping/pong 可能是一个不错的选择,因为它仅在主动发送消息时才保持会话打开,从而防止会话过早关闭。
【讨论】:
首先你必须通过继承引入一个类作为你的用户类,然后像这样使用它:
if (userObject instanceof User) {
User user = (User) userObject;
if (user.getId().equals(userDTO.getId())) {
for (SessionInformation information : sessionRegistry.getAllSessions(user, true)) {
information.expireNow();
}
}
}
【讨论】: