sockjs-client是从SockJS中分离出来的用于客户端使用的通信模块.所以我们就直接来看看SockJS. SockJS是一个浏览器的JavaScript库,它提供了一个类似于网络的对象,SockJS提供了一个连贯的,跨浏览器的JavaScriptAPI,它在浏览器和Web服务器之间创建了一个低延迟,全双工,跨域通信通道. 你可能会问,我为什么不直接用原生的WebSocket而要使用SockJS呢?这得益于SockJS的一大特性,一些浏览器中缺少对WebSocket的支持,因此,回退选项是必要的,而Spring框架提供了基于SockJS协议的透明的回退选项。SockJS提供了浏览器兼容性,优先使用原生的WebSocket,如果某个浏览器不支持WebSocket,SockJS会自动降级为轮询.
SockJS模仿WebSockets API,但它不是WebSocket,而是一个SockJS Javascript对象。首先,您需要加载SockJS JavaScript库。例如,你可以把它放在你的HTML head里:
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
STOMP(Simple Text-Orientated Messaging Protocol) 面向消息的简单文本协议; WebSocket是一个消息架构,不强制使用任何特定的消息协议,它依赖于应用层解释消息的含义. 与HTTP不同,WebSocket是处在TCP上非常薄的一层,会将字节流转化为文本/二进制消息,因此,对于实际应用来说,WebSocket的通信形式层级过低,因此,可以在 WebSocket 之上使用STOMP协议,来为浏览器 和 server间的 通信增加适当的消息语义。
创建spring boot 项目,并且依赖勾选 Thymeleaf
(前端使用的是模板引擎,你也可以些其他的,只要导入sockjs库就行)
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.18version>
<scope>providedscope>
dependency>
dependencies>
WebSocket
配置import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
import org.springframework.web.socket.messaging.StompSubProtocolErrorHandler;
/**
* @Author: OF //作者及
* @Date: 2019-09-04 10:00//完成日期
* @Description: //初始化WebSocket
* @Version: // 版本信息
* @Function // 主要函数及其功能
* @Others: // 其它内容的说明
* @History: // 历史修改记录
*/
@Configuration
/**
* @EnableWebSocketMessageBroker
* 开启使用 STOMP 协议来传输基于代理的消息,Broker是代理
*/
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer
{
@Override
public void registerStompEndpoints(StompEndpointRegistry registry)
{
/**
* setErrorHandler: 设置一个错误处理的 Handler, 以便捕捉错误信息
* addEndpoint: 切入点, 客户端在 new SockJs 的时候用到
* setAllowedOrigins:设置为「*」表示接收 http 和 https 的请求
* withSockJS: 使用 SockJS
*/
registry.setErrorHandler(this.webSocketHandler())
.addEndpoint("/endpointNiu")
.setAllowedOrigins("*")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry)
{
/**
* 参数是多个 destinationPrefixes, 服务端发送消息的 destination 要有这些前缀
*/
registry.enableSimpleBroker("/topic", "/queue");
/**
* 设置点对点时, destination 的前缀, 如客户端订阅
*/
registry.setUserDestinationPrefix("/user");
}
/**
* WebSocket Error 处理
*
* @return WebSocket Error 处理器
*/
@Bean
public StompSubProtocolErrorHandler webSocketHandler() {
return new WebSocketErrorHandler();
}
}
WebSocketErrorHandler.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.Message;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.web.socket.messaging.StompSubProtocolErrorHandler;
/**
* @Author: OF //作者及
* @Date: 2019-09-04 10:18//完成日期
* @Description: // 描述
* @Version: // 版本信息
* @Function // 主要函数及其功能
* @Others: // 其它内容的说明
* @History: // 历史修改记录
*/
@Slf4j
public class WebSocketErrorHandler extends StompSubProtocolErrorHandler
{
public WebSocketErrorHandler()
{
super();
}
@Override
public Message<byte[]> handleClientMessageProcessingError(Message<byte[]> clientMessage, Throwable ex) {
log.error("handleClientMessageProcessingError:clientMessage-" + clientMessage + ", error-"+ex.getMessage());
return super.handleClientMessageProcessingError(clientMessage, ex);
}
@Override
public Message<byte[]> handleErrorMessageToClient(Message<byte[]> errorMessage) {
log.error("handleErrorMessageToClient:errorMessage-" + errorMessage);
return super.handleErrorMessageToClient(errorMessage);
}
@Override
protected Message<byte[]> handleInternal(StompHeaderAccessor errorHeaderAccessor, byte[] errorPayload, Throwable cause, StompHeaderAccessor clientHeaderAccessor) {
log.error("handleInternal:errorHeaderAccessor-" + errorHeaderAccessor + ", errorPayload-" + errorPayload + ", error-" + cause.getMessage() + ", clientHeaderAccessor-"+clientHeaderAccessor);
return super.handleInternal(errorHeaderAccessor, errorPayload, cause, clientHeaderAccessor);
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @Author: OF //作者及
* @Date: 2019-09-04 10:24//完成日期
* @Description: // 描述
* @Version: // 版本信息
* @Function // 主要函数及其功能
* @Others: // 其它内容的说明
* @History: // 历史修改记录
*/
@Controller
@Slf4j
public class WebController
{
@Autowired
private SimpMessagingTemplate messagingTemplate;
/**
* 接收消息
* @param name 姓名
* @return welcome, [姓名] !
*
* @MessageMapping 类似于 @RequestMapping, 只不过映射的是 webSocket 的请求地址
* @SendTo("/topic/getBro") 指定该方法响应给哪个 topic, 客户端订阅了 /topic/getBro 的都能收到方法响应
*/
@MessageMapping("/welcome")
@SendTo("/topic/getBro")
public String say(String name) {
log.info("name: " + name);
return "welcome, " + name + " !";
}
/**
* 广播式发送消息给订阅了「/topic/getBro」的客户端
*/
@RequestMapping("sendMsgBro")
@ResponseBody
public void sendMsg() {
messagingTemplate.convertAndSend("/topic/getBro", "服务器主动推送的广播消息");
}
/**
* 发送消息给指定 sessionId 的客户端, 且该客户端订阅了「/topic/getBro」
*
* @param sessionId 客户端的 sessionId
*/
@RequestMapping("sendMsgPoint")
@ResponseBody
public void sendMsgPoint(String sessionId) {
messagingTemplate.convertAndSendToUser(sessionId, "/queue/getPoint", "服务器主动推送的点对点消息", createHeaders(sessionId));
}
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectEvent;
/**
* @Author: OF //作者及
* @Date: 2019-09-04 10:27//完成日期
* @Description: // 新客户端连接监听器
* @Version: // 版本信息
* @Function // 主要函数及其功能
* @Others: // 其它内容的说明
* @History: // 历史修改记录
*/
@Slf4j
@Component
public class WebSocketConnectListener implements ApplicationListener<SessionConnectEvent>
{
@Override
public void onApplicationEvent(SessionConnectEvent sessionConnectEvent)
{
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(sessionConnectEvent.getMessage());
String sessionId = accessor.getSessionId();
log.info("sessionId: {} 已连接", sessionId);
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
/**
* @Author: OF //作者及
* @Date: 2019-09-04 10:31//完成日期
* @Description: // 断开连接监听器
* @Version: // 版本信息
* @Function // 主要函数及其功能
* @Others: // 其它内容的说明
* @History: // 历史修改记录
*/
@Slf4j
@Component
public class WebSocketDisconnectListener implements ApplicationListener<SessionDisconnectEvent>
{
@Override
public void onApplicationEvent(SessionDisconnectEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
String sessionId = sha.getSessionId();
log.info("sessionId: {} 已断开", sessionId);
}
}
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>WebSockettitle>
<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,浏览器不支持WebSocketh2>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';
// $("#connect").disabled = connected;
// $("#disconnect").disabled = !connected;
$("#response").html();
}
function connect() {
var socket = new SockJS('/endpointNiu');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected:' + frame);
// 订阅 /user/queue/getPoint
stompClient.subscribe('/user/queue/getPoint', function (response) {
showResponse("getPoint " + response.body);
});
// 订阅 /topic/getBro
stompClient.subscribe('/topic/getBro', function (response) {
showResponse("getBro " + response.body);
})
});
}
// 断开连接
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", {}, name);
}
// 显示message
function showResponse(message) {
$("#response").html(message);
}
script>
body>
html>
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @Author: OF //作者及
* @Date: 2019-09-04 10:41//完成日期
* @Description: // 描述
* @Version: // 版本信息
* @Function // 主要函数及其功能
* @Others: // 其它内容的说明
* @History: // 历史修改记录
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport
{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/ws").setViewName("/ws");
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**")
.addResourceLocations("classpath:/static/js/");
}
}
以上代码借鉴该文章
SpringBoot+Webocket 初步使用
// 安装并引入相关模块
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';
export default {
data() {
return {
dataList: []
};
},
mounted:function(){
this.initWebSocket();
},
beforeDestroy: function () {
// 页面离开时断开连接,清除定时器
this.disconnect();
clearInterval(this.timer);
},
methods: {
initWebSocket() {
this.connection();
let self = this;
// 断开重连机制,尝试发送消息,捕获异常发生时重连
this.timer = setInterval(() => {
try {
self.stompClient.send("test");
} catch (err) {
console.log("断线了: " + err);
self.connection();
}
}, 5000);
},
removeTab(targetName) {
console.log(targetName)
},
connection() {
// 建立连接对象
this.socket = new SockJS('http://xxxxxx:8089/ws');//连接服务端提供的通信接口,连接以后才可以订阅广播消息和个人消息
// 获取STOMP子协议的客户端对象
this.stompClient = Stomp.over(this.socket);
// 定义客户端的认证信息,按需求配置
var headers = {
login: 'mylogin',
passcode: 'mypasscode',
// additional header
'client-id': 'my-client-id'
};
// 向服务器发起websocket连接
this.stompClient.connect(headers,(frame) => {
this.stompClient.subscribe('/topic/chat_msg', (msg) => { // 订阅服务端提供的某个topic
consolel.log(msg.body); // msg.body存放的是服务端发送给我们的信息
});
}, (err) => {
// 连接发生错误时的处理函数
console.log(err);
});
},
// 断开连接
disconnect() {
if (this.stompClient != null) {
this.stompClient.disconnect();
console.log("Disconnected");
}
}
}
};
参考:
在vue中使用SockJS实现webSocket通
STOMP 客户端 API 整理