文章目录
- 前言
- 一、客户端信息实体类
- 二、自定义握手管理器
- 三、WebSocket配置类
- 四、WebSocket事件监听类
- 总结
前言
对于SpringBoot而言,使用传统WebSocket监听客户端状态想必大家已经很熟悉了,比如@onClose和@onOpen注解可以用来监听客户端连线状态,但SockJs在使用上并没有可直接使用的注解或方法对客户端进行监听,这边提供了一些方法,看起来也并不复杂。
以下让我们来了解一下SpringBoot的SockJs如何来对客户端进行监听。
提示:以下是本篇文章正文内容,下面案例可供参考
一、客户端信息实体类
首先,先创建一个实体类 StompPrincipal并继承 Principal 接口 ,用于定义和保存客户端信息。
提示:这边使用到Lombok注解,详细使用方式可CSDN进行查询。
/**
* 客户端信息实体类
*
* @author Tony Peng
* @date 2022/10/28 8:45
*/
@Data
@AllArgsConstructor
public class StompPrincipal implements Principal {
/**
* 客户端主机名称,对应 HostName
*/
private String name;
/**
* 客户端主机IP地址,对应 HostAddress
*/
private String publicName;
}
二、自定义握手管理器
接下来,我们新建一个 CustomHandshakeHandler (自定义握手管理器)类,继承自 DefaultHandshakeHandler (默认握手管理器)。
重写 determineUser 方法,从 ServerHttpRequest 对象中可以获取到客户端信息。
这边我们获取客户端名称和客户端IP地址进行定义和保存。
/**
* WebSocket自定义握手管理器
*
* @author Tony Peng
* @date 2022/10/21 11:19
*/
public class CustomHandshakeHandler extends DefaultHandshakeHandler {
/**
* 重写定义用户信息方法
*
* @param request 握手请求对象
* @param wsHandler WebSocket管理器,用于管理信息
* @param attributes 用于传递WebSocket会话的握手属性
* @return StompPrincipal 自定义用户信息
*/
@Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
//获取客户端主机名称
String hostName = request.getRemoteAddress().getHostName();
//获取客户端主机IP地址
String hostAddress = request.getRemoteAddress().getAddress().getHostAddress();
//StompPrincipal(name = hostName, publicName = hostAddress)
return new StompPrincipal(hostName, hostAddress);
}
}
这样我们继承并重写了握手管理器后,我们就可以对每个通过SockJs连接的客户端的信息进行定义和保存了。
三、WebSocket配置类
这边我们要实现 WebSocketMessageBrokerConfigurer 接口的 configureMessageBroker 方法和 registerStompEndpoints 方法。
代码如下:
/**
* WebSocket配置类
*
* @author Tony Peng
* @date 2022/10/28 11:00
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketMessageConfig implements WebSocketMessageBrokerConfigurer {
/**
* 代理配置
*
* @param config 配置对象
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/topic");
}
/**
* 注册STOMP协议节点并映射指定url
*
* @param registry 注册对象
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/socket").setAllowedOriginPatterns("*")
//这边我们要设置自定义握手管理器才会生效
.setHandshakeHandler(new CustomHandshakeHandler())
.withSockJS();
}
}
四、WebSocket事件监听类
我们新建一个 WebSocketEventListener 类,用来监听客户端状态事件。
这边我们用到了Spring提供的一个注解 @EventListener。
这个注解为我们监听客户端状态起到关键作用。
接下来我们用此注解来监听 WebSocket Session Event(WebSocket会话事件)
以下代码演示获取四个事件:
- SessionConnectedEvent(对应WebSocket 的 @OnOpen 注解)
- SessionDisconnectEvent (对应WebSocket 的 @OnClose 注解)
- SessionSubscribeEvent (订阅事件会话)
- SessionUnsubscribeEvent (取消订阅事件会话)
提示:这边使用到 @Componenet 组件扫描注解和 @Slf4j 日志注解。
//这边使用Hutool的JSON工具类进行JSON解析,详细使用方式详见 hutool.cn
import static cn.hutool.json.JSONUtil.toBean;
import static cn.hutool.json.JSONUtil.toJsonStr;
/**
* WebSocket客户端状态监听
*
* @author Tony Peng
* @date 2022/10/27 11:19
*/
@Slf4j
@Component
public class WebSocketEventListener {
/**
* 监听客户端连接
*
* @param event 连接事件对象
*/
@EventListener
public void handleWebSocketConnectListener(SessionConnectedEvent event) {
StompPrincipal principal = toBean(toJsonStr(event.getUser()), StompPrincipal.class);
log.info("WebSocket 客户端已连接: {}",
"{ 客户端主机名: " + principal.getName() +
", 客户端主机IP地址: " + principal.getPublicName() +
", 会话ID: " + requireNonNull(event.getMessage().getHeaders().get("simpSessionId")) + " }");
}
/**
* 监听客户端关闭事件
*
* @param event 关闭事件对象
*/
@EventListener
public void handleWebSocketCloseListener(SessionDisconnectEvent event) {
StompPrincipal principal = toBean(toJsonStr(event.getUser()), StompPrincipal.class);
log.info("WebSocket 客户端已关闭: {}",
"{ 客户端主机名: " + principal.getName() +
", 客户端主机IP地址: " + principal.getPublicName() +
", 会话ID: " + requireNonNull(event.getMessage().getHeaders().get("simpSessionId")) + " }");
}
/**
* 监听客户端订阅事件
*
* @param event 订阅事件对象
*/
@EventListener
public void handleSubscription(SessionSubscribeEvent event) {
StompPrincipal principal = toBean(toJsonStr(event.getUser()), StompPrincipal.class);
log.info("WebSocket 客户端已订阅: {}",
"{ 客户端主机名: " + principal.getName() +
", 客户端主机IP地址: " + principal.getPublicName() +
", 订阅节点: " + requireNonNull(event.getMessage().getHeaders().get("simpDestination")) + " }");
}
/**
* 监听客户端取消订阅事件
*
* @param event 取消订阅事件对象
*/
@EventListener
public void handleUnSubscription(SessionUnsubscribeEvent event) {
StompPrincipal principal = toBean(toJsonStr(event.getUser()), StompPrincipal.class);
log.info("WebSocket 客户端已取消订阅: {}",
"{ 客户端主机名: " + principal.getName() +
", 客户端主机IP地址: " + principal.getPublicName() +
", 取消订阅节点: " + requireNonNull(event.getMessage().getHeaders().get("simpDestination")) + " }");
}
}
来说明一下为什么客户端信息不能从 event.getUser() 直接获取客户端信息。
首先, event.getUser() 返回的是 Principal 类,这个类只有 getName() 这个方法,所以只能获取 name 字段。
在事件里,实体类映射的是 StompPrincipal 对象, 所以此时你打印 event.getUser() 时,可以看到其实是 StompPrincipal 对象,里面是有 name 和 publicName 这两个字段,所以这边对 even.getUser() 重新进行解析,就可以获取到 name (客户端主机名称)和 publicName (客户端主机IP地址)了。
总结
最近一直在使用Stomp + SockJs进行前后端通讯,发现没有像传统WebSocket那样有注解直接监听客户端状态,网上百度或在CSDN查询都无果,接着研究了几天关于WebSocket + Stomp + SockJs后端消息机制,可以用Spring提供的 @EventListener 来监听客户端对象,配置类也提供了拦截握手信息的 HandShakerHandler 握手管理器,自己定义一下就可以轻松获取客户端状态了,这样就可以让你的程序更加智能化,通过判断客户端状态采取各种措施。