WebSocke介绍
WebSocket协议是应用程序处理实时消息的方法之一。最常见的替代方案是长轮询(long polling)和服务器推送事件(server-sent events)。
目录结构大概是这样的
pom
<!-- thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- web 启动类 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- WebSocket 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- test 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
JAVA代码
1配置基于STOMP的websocket
@Configuration
@EnableWebSocketMessageBroker//注解表示开启使用STOMP协议来传输基于代理的消息,Broker就是代理的意思。
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
//方法表示注册STOMP协议的节点,并指定映射的URL
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//用来注册STOMP协议节点,同时指定使用SockJS
registry.addEndpoint("/endpoint")
.setAllowedOrigins("*") //解决跨域问题
.withSockJS();
}
//用来配置消息代理,由于我们是实现推送功能,这里的消息代理是/queue
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//点对点应配置一个/user消息代理,广播式应配置一个/topic消息代理
registry.enableSimpleBroker("/topic","/user");
// 客户端向服务端发送消息需有/app 前缀
// registry.setApplicationDestinationPrefixes("/app");
//点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/
registry.setUserDestinationPrefix("/user");
}
}
2客户端发往服务器消息实体类
/**
* 客户端发往服务器消息实体
*/
public class Message {
private String name;
public String getName() {
return name;
}
}
3服务器发往客户端消息实体类
/**
* 服务器发往客户端消息实体
*/
public class Response {
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
private String responseMessage;
public Response(String responseMessage) {
this.responseMessage = responseMessage;
}
public String getResponseMessage() {
return responseMessage;
}
}
4User
public class User {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
5页面控制
//注意,这里使用的是 @Controller 注解,用于匹配 html 前缀,加载页面。
@Controller
public class ViewController {
@GetMapping("/index")
public String getViewIndex(){
return "index";
}
@GetMapping("/queue")
public String getView(){
return "queue";
}
@GetMapping("/response")
public String getViewResponse(){
return "response";
}
}
6 controller
@RestController
public class WebSocketController {
@Autowired
SimpMessagingTemplate simpMessagingTemplate;
//广播推送消息
@MessageMapping("/hello") // @MessageMapping 和 @RequestMapping 功能类似,浏览器向服务器发起消息,映射到该地址。
@SendTo("/topic/say") // 如果服务器接受到了消息,就会对订阅了 @SendTo 括号中的地址的浏览器发送消息。
public Response say(Message message) throws Exception {
Thread.sleep(3000);
return new Response("Hello," + message.getName() + "!");
}
//广播推送消息,作用同上
@MessageMapping("/response")
public void sendTopicMessage() {
System.out.println("后台广播推送!");
User user=new User();
user.setName("杨杰");
user.setAge(10);
simpMessagingTemplate.convertAndSend("/topic/response",user);
}
//聊天
@MessageMapping("/chat")
public void sendMsg(){
System.out.println("后台一对一推送!");
User user=new User();
user.setId(1);
user.setName("oyzc");
user.setAge(10);
simpMessagingTemplate.convertAndSendToUser(user.getId()+"","/user/chat",user);
}
}
HTML页面
1queue.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Spring Boot+WebSocket+广播式</title>
</head>
<body onload="disconnect()">
<noscript><h2 style="color: #ff0000">貌似你的浏览器不支持websocket</h2></noscript>
<div>
<div>
<button id="connect" onclick="connect();">连接</button>
<button id="disconnect" disabled="disabled" onclick="disconnect();">断开连接</button>
</div>
<div id="conversationDiv">
<label>输入你的名字</label><input type="text" id="name" />
<button id="sendName" onclick="sendName();">发送</button>
<p id="response"></p>
</div>
</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/sockjs-client/1.4.0/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script type="text/javascript">
var stompClient = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
$('#response').html();
}
function connect() {
// 连接 SockJs 的 endpoint 名称为 "/endpointNasus"
var socket = new SockJS('/endpoint');
// 使用 STOMP 子协议的 WebSocket 客户端
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
// 通过 stompClient.subscribe 订阅 /queue 目标发送的信息,对应控制器的 SendTo 定义
stompClient.subscribe('/topic/say', function(respnose){
// 展示返回的信息,只要订阅了 /queue 目标,都可以接收到服务端返回的信息
showResponse(JSON.parse(respnose.body).responseMessage);
});
});
}
function disconnect() {
// 断开连接
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
// 向服务端发送消息
var name = $('#name').val();
// 通过 stompClient.send 向 /hello (服务端)发送信息,对应控制器 @MessageMapping 中的定义
stompClient.send("/hello", {}, JSON.stringify({ 'name': name }));
}
function showResponse(message) {
// 接收返回的消息
var response = $("#response");
response.html(message);
}
</script>
</body>
</html>
2index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>websocket.html</title>
<meta name="keywords" content="keyword1,keyword2,keyword3">
<meta name="description" content="this is my page">
<meta name="content-type" content="text/html" charset="UTF-8">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
</head>
<body onload="disconnect()">
<div>
<button id="connect" onclick="connect();">连接</button>
<button id="disconnect" disabled="disabled" onclick="disconnect();">断开连接</button>
<div id="conversationDiv">
<p>模拟发送消息</p>
<button id="sendName" onclick="sendName();">发送</button>
</div>
<p id="response"></p>
</div>
<!-- 独立JS -->
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/sockjs-client/1.4.0/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script type="text/javascript">
var stompClient = null;
//加载完浏览器后 调用connect(),打开双通道
(function(){
//打开双通道
connect()
})
//强制关闭浏览器 调用websocket.close(),进行正常关闭
window.onunload = function() {
disconnect()
}
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
$('#response').html();
}
function connect(){
var userId=1;
var socket = new SockJS('/endpoint'); //连接SockJS的endpoint名称为"endpointOyzc"
stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端
stompClient.connect({},function(frame){//连接WebSocket服务端
console.log('Connected:' + frame);
setConnected(true);
//通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息
stompClient.subscribe('/user/' + userId + '/user/chat',function(response){
console.log('1:'+response);
showResponse(JSON.parse(response.body))
});
});
}
function sendName() {
// 向服务端发送消息
// 通过 stompClient.send 向 /hello (服务端)发送信息,对应控制器 @MessageMapping 中的定义
stompClient.send("/chat", {}, {});
}
//关闭双通道
function disconnect(){
if(stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function showResponse(message){
var response = $("#response");
response.append("<p>只有userID为"+message.id+"的人才能收到</p>");
}
</script>
</body>
</html>
3response.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>websocket.html</title>
<meta name="keywords" content="keyword1,keyword2,keyword3">
<meta name="description" content="this is my page">
<meta name="content-type" content="text/html" charset="UTF-8">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
</head>
<body onload="disconnect()">
<div>
<button id="connect" onclick="connect();">连接</button>
<button id="disconnect" disabled="disabled" onclick="disconnect();">断开连接</button>
<div id="conversationDiv">
<p>模拟发送消息</p>
<button id="sendName" onclick="sendName();">发送</button>
</div>
<p id="response"></p>
</div>
<!-- 独立JS -->
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/sockjs-client/1.4.0/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script type="text/javascript">
var stompClient = null;
//加载完浏览器后 调用connect(),打开双通道
(function(){
//打开双通道
connect()
})
//强制关闭浏览器 调用websocket.close(),进行正常关闭
window.onunload = function() {
disconnect()
}
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
$('#response').html();
}
function sendName() {
// 向服务端发送消息
// 通过 stompClient.send 向 /hello (服务端)发送信息,对应控制器 @MessageMapping 中的定义
stompClient.send("/response", {}, {});
}
function connect(){
var socket = new SockJS('/endpoint'); //连接SockJS的endpoint名称为"endpointOyzc"
stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端
stompClient.connect({},function(frame){//连接WebSocket服务端
console.log('Connected:' + frame);
setConnected(true);
//通过stompClient.subscribe订阅/topic/response 目标(destination)发送的消息
stompClient.subscribe('/topic/response',function(response){
console.log('1:'+response);
showResponse(JSON.parse(response.body))
});
});
}
//关闭双通道
function disconnect(){
if(stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function showResponse(message){
var response = $("#response");
response.append("<p>用户名为:"+message.name+"</p>");
}
</script>
</body>
</html>