上一篇我们介绍了使用javax.websocket来实现websocket,现在我们基本使用的都是SpringBoot,今天,我们来学习一下如何在springboot中实现websocket
一、javax.websocket
javax.websockethttps://blog.csdn.***/u014374743/article/details/135905010二、spring-boot-starter-websocket
优点:
-
集成性:Spring Boot提供的WebSocket模块简化了WebSocket的集成,提供了更易用的编程模型。
-
灵活性:基于Spring框架,可以利用Spring的依赖注入、AOP等特性,实现更灵活的定制和管理。
劣势:
-
依赖复杂:引入Spring Boot框架可能增加项目的复杂性和启动时间。
-
可定制性差:有时对于特定定制需求,可能需要更多的配置和处理。
实现方式:
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.新增User类用于记录访问客户端身份
import lombok.Data;
import java.security.Principal;
@Data
public class User implements Principal {
private String name;
private String id;
public User() {
}
public User(String id, String name) {
this.id = id;
this.name = name;
}
}
3.增加拦截器保存已连接的用户
import ***.maple.websocket.demos.web.User;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.Stomp***mand;
import org.springframework.messaging.simp.stomp.StompHeaderA***essor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderA***essor;
import org.springframework.stereotype.***ponent;
@***ponent
public class WebSocketInterceptor implements ChannelInterceptor {
private static final String USER_ID = "User-ID";
private static final String USER_NAME = "User-Name";
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderA***essor a***essor = MessageHeaderA***essor.getA***essor(message, StompHeaderA***essor.class);
// 如果是连接请求,记录userId
if (Stomp***mand.CONNECT.equals(a***essor.get***mand())) {
String userID = a***essor.getFirstNativeHeader(USER_ID);
String username = a***essor.getFirstNativeHeader(USER_NAME);
a***essor.setUser(new User(userID, username));
}
return message;
}
}
4.创建websocket配置类
定义代理订阅、发送前缀,注册服务,添加拦截器
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@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) {
// 注册端点,客户端将使用此端点连接WebSocket
registry.addEndpoint("/websocket").withSockJS();
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
// 拦截器配置
registration.interceptors(new WebSocketInterceptor());
}
}
5.后端Controller控制器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.user.SimpUser;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Set;
@Controller
public class WebSocketController {
@Autowired
private SimpMessagingTemplate messagingTemplate;
@Autowired
private SimpUserRegistry userRegistry;
// 接收来自客户端的消息,并发送回复消息给特定的客户端频道或房间
@MessageMapping("/sendMessage/{clientId}")
@SendTo("/topic/reply/{clientId}")
public String sendMessageToClient(@DestinationVariable String clientId, String message) {
String msg = "收到客户端:" + clientId + "发送的消息:" + message;
System.out.println(msg);
return msg;
}
// 获取已连接用户的信息
@GetMapping("/connectedUsers")
@ResponseBody
public HashMap<String, User> getConnectedUsers() {
HashMap<String, User> result = new HashMap<>();
Set<SimpUser> users = userRegistry.getUsers();
for (SimpUser user : users) {
result.put(user.getName(), (User) user.getPrincipal());
}
return result;
}
// 后端主动向特定客户端推送消息
@GetMapping("/sendToClient")
@ResponseBody
public void sendToClient(@RequestParam String clientId) {
messagingTemplate.convertAndSendToUser(clientId, "/topic/push", "Hello, Client " + clientId + "! 这是只给你的信息.");
messagingTemplate.convertAndSend("/topic/pushAll", "Hello! 这是群发消息.");
}
}
6.前端测试代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Example</title>
<script src="/js/sockjs.min.js"></script>
<script src="/js/stomp.min.js"></script>
</head>
<body>
<div>
<input type="text" id="messageInput" placeholder="输入消息">
<button onclick="sendMessage()">发送消息</button>
</div>
<script>
//这里是随机生成的clientId
const clientId = generateRandomId(5);
function generateRandomId(length) {
let id = '';
for (let i = 0; i < length; i++) {
const randomCharCode = Math.floor((Math.random() * 26)) + 97; // ASCII码范围为a-z
id += String.fromCharCode(randomCharCode);
}
return id;
}
const socket = new SockJS('http://localhost:8080/websocket');
const stompClient = Stomp.over(socket);
stompClient.connect({"User-ID":clientId,"User-Name":clientId}, function (frame) {
console.log('WebSocket 连接已建立');
// 用实际的客户端标识符替换
// 订阅特定客户端的频道或房间
stompClient.subscribe(`/topic/reply/${clientId}`, function (response) {
console.log('收到回复:' + response.body);
});
//后端通过toUser指定发送信息时,前端订阅的目的地需要加上/user前缀
stompClient.subscribe(`/user/topic/push`, function (response) {
console.log('收到后台推送:' + response.body);
});
stompClient.subscribe(`/topic/pushAll`, function (response) {
console.log('收到后台推送:' + response.body);
});
});
// 发送消息给特定客户端的示例
function sendMessage() {
const messageInput = document.getElementById('messageInput');
const message = messageInput.value;
// 用实际的客户端标识符替换
stompClient.send(`/app/sendMessage/${clientId}`, {}, message);
// 清空输入框
messageInput.value = '';
}
</script>
</body>
</html>
7.测试结果
这里打开3个客户端,分别发送一段文字,后台也会同时响应返回信息
后台返回的结果:
接下来测试后端接口,我们可以先获取下目前在线的用户:
通过http://localhost:8080/connectedUsers接口
可以看到正是我们刚才连接的三个用户,这时候我们调用接口给前端qzpmp用户发送信息,并且给所有用户通知一条信息
我们可以查看下结果
可以看到只有一个用户收到了我们单独发送的信息,以上就是这次分享了。
如果对你有帮助,点击关注一下,是我分享的动力。
关注我,学习更多简单好理解的编程和架构知识!
下一篇主要讲解通过***ty方式实现websocket
关注我 不迷路
或者微信 添加公众号,发送websocket获取教程源码