alexpdh's blog

Spring Boot 集成 WebSocket 基于 STOMP 实现消息实时通信

websocket-java.jpg


概述

Spring Boot

Spring Boot:是为了简化使用 Spring 开发的应用产生的,它使用“习惯由于配置”的理念使项目快速运行起来。使用 Spring Boot 你可以不用或者只需要很少的 Spring 配置,你可以仅仅使用一个 java -jar 命令就运行起一个 java 项目。

优点

  • 快速构建项目。
  • 开箱即用,提供各种默认配置来简化项目配置。
  • 项目可以独立运行,无须外部依赖 Servlet 容器。
  • 对主流框架的无配置集成。
  • 提供各种 starter 简化 Maven 配置。

WebSocket

WebSocket:是 HTML5 开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 WebSocket 通信协议于2011年被IETF定为标准RFC 6455,WebSocketAPI 被W3C定为标准。 在WebSocket API 中,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

特点

  • 为浏览器和服务端提供了双工异步通信的功能,即服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
  • 建立在 TCP 协议之上,服务器端的实现比较容易。
  • 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
  • 数据格式比较轻量,性能开销小,通信高效。
  • 可以发送文本,也可以发送二进制数据。
  • 没有同源限制,客户端可以与任意服务器通信。
  • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

STOMP

STOMP:即Simple (or Streaming) Text Orientated Messaging Protocol,它是一个简单的文本消息传输协议,属于 WebSocket 的子协议,提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。

优点

  • 使用 WebSocket 需要增加浏览器的兼容支持,如 IE 10+、Chrome 13+、Firefox 6+,而 STOMP 是一个更高级的协议,它使用一个基于帧(frame)的格式来定义消息,与 HTTP 的 request 和 response 类似。

项目构建

添加 WebSocket 和 Thymeleaf 依赖

springboot-socket-01.jpg

新建 websocket 配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.socket.websocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
/**
* websocket 配置类
*
* @author pengdh
* @date: 2017-09-02 15:46
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/endpointWeb").withSockJS();;
}
@Override
public void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry) {
messageBrokerRegistry.enableSimpleBroker("/topic");
}
}
  1. 通过 @EnableWebSocketMessageBroker 注解来开启使用 STOMP 协议来传输基于代理(message broker)的消息。
  2. 通过复写父类的 registerStompEndpoints 方法,注册 STOMP 协议的节点来映射指定的 URL 并指定使用 SocketJS 协议。
  3. 通过复写父类的 configureMessageBroker 方法,来配置一个广播式消息代理。

新建浏览器像服务端发送消息封装类 ClientRequestMessage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.socket.websocket;
/**
* 浏览器像服务端发送消息封装类
*
* @author pengdh
* @date: 2017-09-02 16:10
*/
public class ClientRequestMessage {
private String name;
public String getName() {
return name;
}
}

新建服务端像浏览器端发送消息封装类 ServerResponseMessage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.socket.websocket;
/**
* 服务端像浏览器端发送消息封装类
*
* @author pengdh
* @date: 2017-09-02 16:13
*/
public class ServerResponseMessage {
private String responseMessage;
public ServerResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
public String getResponseMessage() {
return responseMessage;
}
}

新建请求控制器 WsController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.socket.websocket;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
/**
* 请求控制器
*
* @author pengdh
* @date: 2017-09-02 16:16
*/
@Controller
public class WsController {
@MessageMapping("/welcome")
@SendTo("/topic/getResponse")
public ServerResponseMessage say(ClientRequestMessage message) {
System.out.println(">>>>>>>>>>>>>> " + message.getName());
return new ServerResponseMessage("Welcome," + message.getName() + "!");
}
}
  1. 通过 @MessageMapping 注解接收浏览器端发送的消息。
  2. 通过 @SendTo 注解像浏览器端广播消息。

构建浏览器端

  • 需要下载 stomp.js、sockjs.min.js、jquery.js 几个脚本支持。将几个文件放在 src/main/resources/static 下。
  • 演示页面 ws.html 放在 src/main/resources/templates 下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>广播式WebSocket</title>
<script th:src="@{js/sockjs.min.js}"></script>
<script th:src="@{js/stomp.js}"></script>
<script th:src="@{js/jquery-3.1.1.js}"></script>
</head>
<body onload="disconnect()">
<noscript><h2 style="color: #e80b0a;">Sorry,浏览器不支持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 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() {
var socket = new SockJS('/endpointWeb');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected:' + frame);
stompClient.subscribe('/topic/getResponse', function (response) {
showResponse(JSON.parse(response.body).responseMessage);
})
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log('Disconnected');
}
function sendName() {
var name = $('#name').val();
console.log('name:' + name);
stompClient.send("/welcome", {}, JSON.stringify({'name': name}));
}
function showResponse(message) {
$("#response").html(message);
}
</script>
</body>
</html>

新建 WebMvcConfig 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.socket.websocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* viewController
* 为ws.html提供路径映射
*
* @author pengdh
* @date: 2017-09-02 16:18
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/ws").setViewName("/ws");
}
}

运行效果

websocket-topic.jpg


参考文献

alexpdh wechat
欢迎扫一扫关注 程序猿pdh 公众号!