需求来源
最近想做一个小工具方便我在不同设备间传递文本,比如我用两台电脑,一台开发用笔记本,一台拨VPN连堡垒机,可能就经常需要将开发机上的脚本、代码文本发送到另一台电脑上就比较麻烦。所以想到搭建一个小站点像一个匿名聊天室,这边发送消息,那边及时收到。所以就准备用websocket做个小应用。
参考了网上其他一些博客文章的整合方式,感觉比较麻烦,于是参考了spring官方的一篇文章去实现
实现方案
参考官方文档:https://spring.io/guides/gs/messaging-stomp-websocket
源码:https://github.com/spring-guides/gs-messaging-stomp-websocket
官方使用了STOMP协议进行消息传递来创建交互式 Web 应用程序。STOMP 是一个较低级别的 WebSocket 之上运行的子协议,这个协议是基于发布订阅模式进行消息传递的,这样我们在代码层面就不用管理连接的session了,各个客户端订阅相关topic,基于topic进行数据传递。
引入以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
然后添加如下配置代码,基本的配置就算完了,非常简单
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket");
}
}
客户端使用了stomp.js,具体可以参考官方的文档
另外如果要监听客户端上线掉线等事件,可以直接监听相关的事件:SessionDisconnectEvent、SessionConnectedEvent、SessionSubscribeEvent等
直接将代码加入到上面的配置类WebSocketConfig就可以
@EventListener
public void onSessionDisconnectEvent(SessionDisconnectEvent sessionDisconnectEvent) {
log.info("断开连接 " + ((Map<?, ?>) sessionDisconnectEvent.getMessage().getHeaders().get("simpSessionAttributes")).get("ip"));
String content = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss : ") + "客户端下线,"
+ ((Map<?, ?>) sessionDisconnectEvent.getMessage().getHeaders().get("simpSessionAttributes")).get("ip");
HelloMessage message = HelloMessage.builder().content(content)
.onlineNum(((SubProtocolWebSocketHandler) subProtocolWebSocketHandler).getStats().getWebSocketSessions())
.action(HelloMessage.ACTION_EVENT).build();
messageManager.addMessage(message);
simpMessagingTemplate.convertAndSend("/topic/greetings", message);
}
@EventListener
public void onSessionConnectedEvent(SessionConnectedEvent sessionConnectedEvent) {
// log.info("连接上 " + ((Map<?, ?>) sessionConnectedEvent.getMessage().getHeaders().get("simpSessionAttributes")).get("ip"));
}
@EventListener
public void onSessionConnectEvent(SessionConnectEvent sessionConnectEvent) {
log.info("连接 " + ((Map<?, ?>) sessionConnectEvent.getMessage().getHeaders().get("simpSessionAttributes")).get("ip"));
}
@EventListener
public void onSessionSubscribeEvent(SessionSubscribeEvent sessionSubscribeEvent) {
String clientIp = (String) ((Map<?, ?>) sessionSubscribeEvent.getMessage().getHeaders().get("simpSessionAttributes")).get("ip");
log.info("订阅 " + clientIp);
String content = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss : ") + "客户端上线," + clientIp;
HelloMessage message = HelloMessage.builder().content(content).onlineNum(((SubProtocolWebSocketHandler) subProtocolWebSocketHandler).getStats().getWebSocketSessions())
.action(HelloMessage.ACTION_EVENT).build();
messageManager.addMessage(message);
simpMessagingTemplate.convertAndSend("/topic/greetings", message);
}
我这里是监听客户端上下线,统计了一下在线人数
最终接收消息就跟写一个普通的Controller差不多:
@Controller
public class GreetingController {
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
}
这里前端发送给“/app/hello”这个topic的消息就会被这个方法收到,处理完后返回的实体类也会序列化转发给"/topic/greetings",前端如果订阅了也会收到。
服务端如果要主动发送消息可以注入SimpMessagingTemplate对象调用它的以下方法:
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
simpMessagingTemplate.convertAndSend("/topic/greetings", message);
最终实现效果可以试试这个:
评论区