Spring Boot中使用WebSocket总结:几种实现方式详解

您所在的位置:网站首页 实现webservice的几种方式 Spring Boot中使用WebSocket总结:几种实现方式详解

Spring Boot中使用WebSocket总结:几种实现方式详解

2024-07-14 00:20:45| 来源: 网络整理| 查看: 265

简介

所谓WebSocket, 类似于Socket,它的作用是可以让Web应用中的客户端和服务端建立全双工通信。在基于Spring的应用中使用WebSocket一般可以有以下三种方式:

使用Java提供的@ServerEndpoint注解实现使用Spring提供的低层级WebSocket API实现使用STOMP消息实现

下面,我将对这三种实现方式做一个简单介绍。

使用Java提供的@ServerEndpoint注解实现 (1)使用@ServerEndpoint注解监听一个WebSocket请求路径:

这里监听了客户端的连接端口/reverse,并定义了如何处理客户端发来的消息

 

package cn.zifangsky.samplewebsocket.websocket; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import java.io.IOException; /** * ReverseWebSocketEndpoint */ @ServerEndpoint("/reverse") public class ReverseWebSocketEndpoint {     @OnMessage     public void handleMessage(Session session, String message) throws IOException {         session.getBasicRemote().sendText("Reversed: " + new StringBuilder(message).reverse());     } }

 

(2)WebSocket相关配置: package cn.zifangsky.samplewebsocket.config; import cn.zifangsky.samplewebsocket.websocket.ReverseWebSocketEndpoint; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * WebSocket相关配置 */ @Configuration @EnableWebSocket public class WebSocketConfig{     @Bean     public ReverseWebSocketEndpoint reverseWebSocketEndpoint() {         return new ReverseWebSocketEndpoint();     }     @Bean     public ServerEndpointExporter serverEndpointExporter() {         return new ServerEndpointExporter();     } }

 

 

(3)示例页面:                         WebSocket Examples: Reverse                             #connect-container {             margin: 0 auto;             width: 400px;         }         #connect-container div {             padding: 5px;             margin: 0 7px 10px 0;         }         .layui-btn {             display: inline-block;         }                   var ws = null;         $(function () {             var target = $("#target");             if (window.location.protocol === 'http:') {                 target.val('ws://' + window.location.host + target.val());             } else {                 target.val('wss://' + window.location.host + target.val());             }         });         function setConnected(connected) {             var connect = $("#connect");             var disconnect = $("#disconnect");             var reverse = $("#reverse");             if (connected) {                 connect.addClass("layui-btn-disabled");                 disconnect.removeClass("layui-btn-disabled");                 reverse.removeClass("layui-btn-disabled");             } else {                 connect.removeClass("layui-btn-disabled");                 disconnect.addClass("layui-btn-disabled");                 reverse.addClass("layui-btn-disabled");             }             connect.attr("disabled", connected);             disconnect.attr("disabled", !connected);             reverse.attr("disabled", !connected);         }         //连接         function connect() {             var target = $("#target").val();             ws = new WebSocket(target);             ws.onopen = function () {                 setConnected(true);                 log('Info: WebSocket connection opened.');             };             ws.onmessage = function (event) {                 log('Received: ' + event.data);             };             ws.onclose = function () {                 setConnected(false);                 log('Info: WebSocket connection closed.');             };         }         //断开连接         function disconnect() {             if (ws != null) {                 ws.close();                 ws = null;             }             setConnected(false);         }         //文字反转         function reverse() {             if (ws != null) {                 var message = $("#message").val();                 log('Sent: ' + message);                 ws.send(message);             } else {                 alert('WebSocket connection not established, please connect.');             }         }         //日志输出         function log(message) {             console.debug(message);         }          Seems your browser doesn't support Javascript! Websockets rely on Javascript being         enabled. Please enable         Javascript and reload this page!                           Reverse                                                                         Connect                 Disconnect                                                                                                       Reverse message                                            

 

启动项目后访问页面,效果如下:

使用Spring提供的低层级WebSocket API实现

Spring 4.0为WebSocket通信提供了支持,包括:

发送和接收消息的低层级API;

发送和接收消息的高级API;

用来发送消息的模板;

支持SockJS,用来解决浏览器端、服务器以及代理不支持WebSocket的问题。

使用Spring提供的低层级API实现WebSocket,主要需要以下几个步骤:

(1)添加一个WebSocketHandler:

定义一个继承了AbstractWebSocketHandler类的消息处理类,然后自定义对”建立连接“、”接收/发送消息“、”异常情况“等情况进行处理

 

package cn.zifangsky.samplewebsocket.websocket; import cn.zifangsky.samplewebsocket.service.EchoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import javax.annotation.Resource; import java.text.MessageFormat; /** * 通过继承 {@link org.springframework.web.socket.handler.AbstractWebSocketHandler} 的示例 */ public class EchoWebSocketHandler extends TextWebSocketHandler{     private final Logger logger = LoggerFactory.getLogger(getClass());     @Resource(name = "echoServiceImpl")     private EchoService echoService;     @Override     public void afterConnectionEstablished(WebSocketSession session) throws Exception {         logger.debug("Opened new session in instance " + this);     }     @Override     protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {         //组装返回的Echo信息         String echoMessage = this.echoService.echo(message.getPayload());         logger.debug(MessageFormat.format("Echo message \"{0}\"", echoMessage));         session.sendMessage(new TextMessage(echoMessage));     }     @Override     public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {         session.close(CloseStatus.SERVER_ERROR);         logger.debug("Info: WebSocket connection closed.");     } }

 

(2)WebSocket相关配置: package cn.zifangsky.samplewebsocket.config; import cn.zifangsky.samplewebsocket.websocket.EchoWebSocketHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; /** * WebSocket相关配置 */ @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer{     @Override     public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {         registry.addHandler(echoWebSocketHandler(), "/echoMessage");         registry.addHandler(echoWebSocketHandler(), "/echoMessage_SockJS").withSockJS();     }     /**      * 通过继承 {@link org.springframework.web.socket.handler.AbstractWebSocketHandler} 的示例      */     @Bean     public WebSocketHandler echoWebSocketHandler(){         return new EchoWebSocketHandler();     } }

 

从上面代码可以看出,这里除了配置了基本的WebSocket(也就是/echoMessage这个连接地址),还使用SockJS配置了浏览器不支持WebSocket技术时的替代方案(也就是/echoMessage_SockJS这个连接地址)。

(3)两个示例页面:

i)echo.html:

                        WebSocket Examples: Reverse                             #connect-container {             margin: 0 auto;             width: 400px;         }         #connect-container div {             padding: 5px;             margin: 0 7px 10px 0;         }         .layui-btn {             display: inline-block;         }                   var ws = null;         $(function () {             var target = $("#target");             if (window.location.protocol === 'http:') {                 target.val('ws://' + window.location.host + target.val());             } else {                 target.val('wss://' + window.location.host + target.val());             }         });         function setConnected(connected) {             var connect = $("#connect");             var disconnect = $("#disconnect");             var echo = $("#echo");             if (connected) {                 connect.addClass("layui-btn-disabled");                 disconnect.removeClass("layui-btn-disabled");                 echo.removeClass("layui-btn-disabled");             } else {                 connect.removeClass("layui-btn-disabled");                 disconnect.addClass("layui-btn-disabled");                 echo.addClass("layui-btn-disabled");             }             connect.attr("disabled", connected);             disconnect.attr("disabled", !connected);             echo.attr("disabled", !connected);         }         //连接         function connect() {             var target = $("#target").val();             ws = new WebSocket(target);             ws.onopen = function () {                 setConnected(true);                 log('Info: WebSocket connection opened.');             };             ws.onmessage = function (event) {                 log('Received: ' + event.data);             };             ws.onclose = function () {                 setConnected(false);                 log('Info: WebSocket connection closed.');             };         }         //断开连接         function disconnect() {             if (ws != null) {                 ws.close();                 ws = null;             }             setConnected(false);         }         //Echo         function echo() {             if (ws != null) {                 var message = $("#message").val();                 log('Sent: ' + message);                 ws.send(message);             } else {                 alert('WebSocket connection not established, please connect.');             }         }         //日志输出         function log(message) {             console.debug(message);         }          Seems your browser doesn't support Javascript! Websockets rely on Javascript being         enabled. Please enable         Javascript and reload this page!                           Echo                                                                         Connect                 Disconnect                                                                                                       Echo message                                            

ii)echo_sockjs.html:

                        WebSocket Examples: Reverse                                  #connect-container {             margin: 0 auto;             width: 400px;         }         #connect-container div {             padding: 5px;             margin: 0 7px 10px 0;         }         .layui-btn {             display: inline-block;         }                   var ws = null;         $(function () {             var target = $("#target");             if (window.location.protocol === 'http:') {                 target.val('http://' + window.location.host + target.val());             } else {                 target.val('https://' + window.location.host + target.val());             }         });         function setConnected(connected) {             var connect = $("#connect");             var disconnect = $("#disconnect");             var echo = $("#echo");             if (connected) {                 connect.addClass("layui-btn-disabled");                 disconnect.removeClass("layui-btn-disabled");                 echo.removeClass("layui-btn-disabled");             } else {                 connect.removeClass("layui-btn-disabled");                 disconnect.addClass("layui-btn-disabled");                 echo.addClass("layui-btn-disabled");             }             connect.attr("disabled", connected);             disconnect.attr("disabled", !connected);             echo.attr("disabled", !connected);         }         //连接         function connect() {             var target = $("#target").val();             ws = new SockJS(target);             ws.onopen = function () {                 setConnected(true);                 log('Info: WebSocket connection opened.');             };             ws.onmessage = function (event) {                 log('Received: ' + event.data);             };             ws.onclose = function () {                 setConnected(false);                 log('Info: WebSocket connection closed.');             };         }         //断开连接         function disconnect() {             if (ws != null) {                 ws.close();                 ws = null;             }             setConnected(false);         }         //Echo         function echo() {             if (ws != null) {                 var message = $("#message").val();                 log('Sent: ' + message);                 ws.send(message);             } else {                 alert('WebSocket connection not established, please connect.');             }         }         //日志输出         function log(message) {             console.debug(message);         }          Seems your browser doesn't support Javascript! Websockets rely on Javascript being         enabled. Please enable         Javascript and reload this page!                           Echo With SockJS                                                                         Connect                 Disconnect                                                                                                       Echo message                                             使用STOMP消息实现

所谓STOMP(Simple Text Oriented Messaging Protocol),就是在WebSocket基础之上提供了一个基于帧的线路格式(frame-based wire format)层。它对发送简单文本消息定义了一套规范格式(STOMP消息基于Text,当然也支持传输二进制数据),目前很多服务端消息队列都已经支持STOMP,比如:RabbitMQ、 ActiveMQ等。

(1)WebSocket相关配置: package cn.zifangsky.stompwebsocket.config; import cn.zifangsky.stompwebsocket.interceptor.websocket.MyChannelInterceptor; import org.springframework.beans.factory.annotation.Autowired; 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; /** * WebSocket相关配置 */ @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{     @Autowired     private MyChannelInterceptor myChannelInterceptor;     @Override     public void registerStompEndpoints(StompEndpointRegistry registry) {         registry.addEndpoint("/stomp-websocket").withSockJS();     }     @Override     public void configureMessageBroker(MessageBrokerRegistry registry) {         //客户端需要把消息发送到/message/xxx地址         registry.setApplicationDestinationPrefixes("/message");         //服务端广播消息的路径前缀,客户端需要相应订阅/topic/yyy这个地址的消息         registry.enableSimpleBroker("/topic");     }     @Override     public void configureClientInboundChannel(ChannelRegistration registration) {         registration.interceptors(myChannelInterceptor);     } }

 

从上面代码可以看出,这里设置了好几个地址,简单解释如下:

首先注册了一个名为/stomp-websocket的端点,也就是STOMP客户端连接的地址。

此外,定义了服务端处理WebSocket消息的前缀是/message,这个地址用于客户端向服务端发送消息(比如客户端向/message/hello这个地址发送消息,那么服务端通过@MessageMapping(“/hello”)这个注解来接收并处理消息)

最后,定义了一个简单消息代理,也就是服务端广播消息的路径前缀(比如客户端监听/topic/greeting这个地址,那么服务端就可以通过@SendTo(“/topic/greeting”)这个注解向客户端发送STOMP消息)。

需要注意的是,上面代码中还添加了一个名为MyChannelInterceptor的拦截器,目的是为了在客户端断开连接后打印一下日志。相关代码如下:

package cn.zifangsky.stompwebsocket.interceptor.websocket; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.stereotype.Component; import java.security.Principal; import java.text.MessageFormat; /** * 自定义{@link org.springframework.messaging.support.ChannelInterceptor},实现断开连接的处理 */ @Component public class MyChannelInterceptor implements ChannelInterceptor{     private final Logger logger = LoggerFactory.getLogger(getClass());     @Override     public void afterSendCompletion(Message message, MessageChannel channel, boolean sent, Exception ex) {         StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);         StompCommand command = accessor.getCommand();         //用户已经断开连接         if(StompCommand.DISCONNECT.equals(command)){             String user = "";             Principal principal = accessor.getUser();             if(principal != null && StringUtils.isNoneBlank(principal.getName())){                 user = principal.getName();             }else{                 user = accessor.getSessionId();             }             logger.debug(MessageFormat.format("用户{0}的WebSocket连接已经断开", user));         }     } }

 

(2)使用@MessageMapping和@SendTo注解处理消息:

@MessageMapping注解用于监听指定路径的客户端消息,而@SendTo注解则用于将服务端的消息发送给监听了该路径的客户端。

package cn.zifangsky.stompwebsocket.controller; import cn.zifangsky.stompwebsocket.model.websocket.Greeting; import cn.zifangsky.stompwebsocket.model.websocket.HelloMessage; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; /** * Greeting */ @Controller public class GreetingController {     @MessageMapping("/hello")     @SendTo("/topic/greeting")     public HelloMessage greeting(Greeting greeting) {         return new HelloMessage("Hello," + greeting.getName() + "!");     } }

 

(3)示例页面:                         WebSocket Examples: Reverse                                       #connect-container {             margin: 0 auto;             width: 400px;         }         #connect-container div {             padding: 5px;             margin: 0 7px 10px 0;         }         .layui-btn {             display: inline-block;         }                   var stompClient = null;         $(function () {             var target = $("#target");             if (window.location.protocol === 'http:') {                 target.val('http://' + window.location.host + target.val());             } else {                 target.val('https://' + window.location.host + target.val());             }         });         function setConnected(connected) {             var connect = $("#connect");             var disconnect = $("#disconnect");             var echo = $("#echo");             if (connected) {                 connect.addClass("layui-btn-disabled");                 disconnect.removeClass("layui-btn-disabled");                 echo.removeClass("layui-btn-disabled");             } else {                 connect.removeClass("layui-btn-disabled");                 disconnect.addClass("layui-btn-disabled");                 echo.addClass("layui-btn-disabled");             }             connect.attr("disabled", connected);             disconnect.attr("disabled", !connected);             echo.attr("disabled", !connected);         }         //连接         function connect() {             var target = $("#target").val();             var ws = new SockJS(target);             stompClient = Stomp.over(ws);             stompClient.connect({}, function () {                 setConnected(true);                 log('Info: STOMP connection opened.');                 //订阅服务端的/topic/greeting地址                 stompClient.subscribe("/topic/greeting", function (greeting) {                     log('Received: ' + JSON.parse(greeting.body).content);                 })             },function () {                 //断开处理                 setConnected(false);                 log('Info: STOMP connection closed.');             });         }         //断开连接         function disconnect() {             if (stompClient != null) {                 stompClient.disconnect();                 stompClient = null;             }             setConnected(false);             log('Info: STOMP connection closed.');         }         //向服务端发送姓名         function sendName() {             if (stompClient != null) {                 var username = $("#username").val();                 log('Sent: ' + username);                 stompClient.send("/message/hello", {}, JSON.stringify({'name': username}));             } else {                 alert('STOMP connection not established, please connect.');             }         }         //日志输出         function log(message) {             console.debug(message);         }          Seems your browser doesn't support Javascript! Websockets rely on Javascript being         enabled. Please enable         Javascript and reload this page!                           STOMP Message With SockJS                                                                         Connect                 Disconnect                                                                                                       Say hello                                            

 

启动项目后访问页面,效果如下:

 



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭