SpringBoot中的WebSocket搭建详解 通俗易懂

您所在的位置:网站首页 springboot框架怎么搭建 SpringBoot中的WebSocket搭建详解 通俗易懂

SpringBoot中的WebSocket搭建详解 通俗易懂

2024-06-03 13:46:56| 来源: 网络整理| 查看: 265

SpringBoot中的WebSocket搭建详解 通俗易懂

SpringBoot中已经集成了websocket,搭建起来很简单,容易上手,废话少说,开始。

一、基础搭建 导入依赖 (首先需要一个SpringBoot的环境,此文章不再赘述) org.springframework.boot spring-boot-starter-websocket //以下两个依赖用在前端搭建的时候用到 org.webjars sockjs-client 1.0.2 org.webjars stomp-websocket 2.3.3

导入依赖后 第一步完成。

二、基本的WebSocket配置

接下来进行一个WebSocket配置 需要创建一个配置类WebSocketConfig,定义全局的配置信息,使用JavaConfig的形式

package com.example.websocket.config; import com.example.websocket.intecepter.HttpHandShakeIntecepter; import com.example.websocket.intecepter.SocketChannelIntecepter; 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.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{ /** * 注册端点,发布或者订阅消息的时候需要连接此端点 * setAllowedOrigins 非必须,*表示允许其他域进行连接 * withSockJS 表示开始sockejs支持 * * 这个方法的作用是添加一个服务端点,来接收客户端的连接。 */ public void registerStompEndpoints(StompEndpointRegistry registry) { //表示添加了一个 /endpoint-websocket 端点,客户端就可以通过这个端点来进行连接。 registry.addEndpoint("/endpoint-websocket").addInterceptors(new HttpHandShakeIntecepter()) .setAllowedOrigins("*").withSockJS(); //withSockJS()的作用是开启SockJS支持, } /** * 配置消息代理(中介) * enableSimpleBroker 服务端推送给客户端的路径前缀 * setApplicationDestinationPrefixes 客户端发送数据给服务器端的一个前缀 * * 这个方法的作用是定义消息代理,通俗一点讲就是设置消息连接请求的各种规范信息。 */ @Override public void configureMessageBroker(MessageBrokerRegistry registry) { //表示客户端订阅地址的前缀信息,也就是客户端接收服务端消息的地址的前缀信息 registry.enableSimpleBroker("/topic"); //接收 //指服务端接收地址的前缀,意思就是说客户端给服务端发消息的地址的前缀 registry.setApplicationDestinationPrefixes("/app"); //发送 //上面两个方法定义的信息其实是相反的,一个定义了客户端接收的地址前缀,一个定义了客户端发送地址的前缀 } @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors( new SocketChannelIntecepter()); } @Override public void configureClientOutboundChannel(ChannelRegistration registration) { registration.interceptors( new SocketChannelIntecepter()); } }

在上述的配置文件中 registerStompEndpoints 方法详解

registerStompEndpoints //这个方法的作用是添加一个服务端点,来接收客户端的连接。 可以通俗的理解为 这就像一个屋子(服务端点),而里面配置就是标明屋子的门牌号

registry.addEndpoint("/endpoint-websocket") // 这里表示添加了一个/endpoint-websocket 的端点,客户端可以通过这个端点来进行连接 可以通俗的理解为 这是上述所说的门牌号

.addInterceptors(new HttpHandShakeIntecepter()) //这个是添加的一个自定义拦截器(基本配置里可以不加) .setAllowedOrigins("*") //经查阅官方文档springwebsocket 4.1.5版本前默认支持跨域访问,之后的版本默认不支持跨域,需要设置

.withSockJS(); //withSockJS()的作用是开启SockJS支持

configureMessageBroker 方法详解

configureMessageBroker //这个方法的作用是定义消息代理,通俗一点讲就是设置消息连接请求的各种规范信息

registry.enableSimpleBroker("/topic"); //接收 topic可自定义 //表示客户端订阅地址的前缀信息,也就是客户端接收服务端消息的地址的前缀信息

registry.setApplicationDestinationPrefixes("/app"); //发送 app可自定义 //指服务端接收地址的前缀,意思就是说客户端给服务端发消息的地址的前缀

上面两个方法定义的信息其实是相反的,一个定义了客户端接收的地址前缀,一个定义了客户端发送地址的前缀。 比较绕,可以看下我下面举的例子。 一个客户端当作一个人,进入了屋子(服务端点)之后,有接收信息和发送信息的能力,但是房子里的人多了起,你一句我一句会让信息错乱,为了要统一规范,发送和接收前都要加上一个标识,也正是上面所定义的“topic”,“app”。

三、前后端编写

上述的基本配置完成后,就可以编写基本的功能了

package com.example.websocket.controller.v3; import com.example.websocket.model.InMessage; import com.example.websocket.model.OutMessage; import com.example.websocket.service.WebSocketService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; @Controller public class V3ChatRoomContoller { /** * 这是 调用convertAndSendToUser来推送消息 */ @Autowired private SimpMessagingTemplate template; /** * @MessageMapping * @param message */ @MessageMapping("/v3/single/chat") public void singleChat(InMessage message) { //意思就是“将给定的对象进行序列化,使用‘MessageConverter’进行包装转化成一条消息,发送到指定的目标”,通俗点讲就是我们使用这个方法进行消息的转发发送! template.convertAndSend("/topic/single/"+message.getTo(), new OutMessage(message.getFrom()+" 发送:"+ message.getContent())); } }

内容详解

SimpMessagingTemplate 这是调用convertAndSendToUser来推送消息,Spring-WebSocket内置的一个消息发送工具,可以将消息发送到指定的客户端

@MessageMapping //Spring提供的一个注解,功能类似@RequestMapping,存在于Controller中的,定义一个消息的基本请求,支持通配符``的url等 详情用法见Web on Servlet Stack

此后台的基础搭建完成,开始前端部分。 WebSocket前端准备工作

前端需要准备两个js文件: sockjs.js和stomp.js

org.webjars sockjs-client 1.0.2 org.webjars stomp-websocket 2.3.3

SockJS: SockJS 是一个浏览器上运行的 JavaScript 库,如果浏览器不支持 WebSocket,该库可以模拟对 WebSocket 的支持,实现浏览器和 Web 服务器之间低延迟、全双工、跨域的通讯通道。

Stomp Stomp 提供了客户端和代理之间进行广泛消息传输的框架。Stomp 是一个非常简单而且易用的通讯协议实现,尽管代理端的编写可能非常复杂,但是编写一个 Stomp 客户端却是很简单的事情,另外你可以使用 Telnet 来与你的 Stomp 代理进行交互。

HTML代码:

DOCTYPE html> Hello WebSocket body { background-color: #f5f5f5; } #main-content { max-width: 940px; padding: 2em 3em; margin: 0 auto 20px; background-color: #fff; border: 1px solid #e5e5e5; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } Seems your browser doesn't support Javascript! Websocket relies on Javascript being enabled. Please enable Javascript and reload this page! 建立连接通道: 连接 断开连接 发送 记录

JS代码:

var stompClient = null; function setConnected(connected) { $("#connect").prop("disabled", connected); $("#disconnect").prop("disabled", !connected); if (connected) { $("#conversation").show(); } else { $("#conversation").hide(); } $("#notice").html(""); } function connect() { var from = $("#from").val(); //找到配置好的服务端点(屋子),建立连接 var socket = new SockJS('/endpoint-websocket'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { setConnected(true); console.log('Connected: ' + frame); stompClient.subscribe('/topic/single/'+from, function (result) { //这个subscribe()方法功能就是定义一个订阅地,址用来接收服务端的信息(在服务端代码中,我们在V3ChatRoomContoller中指定了这个订阅地址“/topic/single”),当收到消息后,在回调函数中处理业务逻辑。 showContent(JSON.parse(result.body)); }); }); } //断开连接 function disconnect() { if (stompClient !== null) { stompClient.disconnect(); } setConnected(false); console.log("Disconnected"); } function sendName() { stompClient.send("/app/v3/single/chat", {}, JSON.stringify({'content': $("#content").val(), 'to':$("#to").val(), 'from':$("#from").val()})); } function showContent(body) { //给记录 添加一条聊天记录 $("#notice").append("" + body.content + " "+new Date(body.time).toLocaleString()+""); } $(function () { $("form").on('submit', function (e) { e.preventDefault(); }); $( "#connect" ).click(function() { connect(); }); $( "#disconnect" ).click(function() { disconnect(); }); $( "#send" ).click(function() { sendName(); }); });

开启Socket(建立通道)

1.var socket = new SockJS('/endpoint-websocket'); //建立一个SocketJS对象 2 stompClient = Stomp.over(socket); //用Stomp将SocketJS进行协议封装 3.stompClient.connect(); //与服务端进行连接,同时有一个回调函数,处理连接成功后的操作信息。

发送消息

stompClient.send("/app/v3/single/chat", {}, value); stompClient.send("/app/v3/single/chat", {}, JSON.stringify({'content': $("#content").val(), 'to':$("#to").val(), 'from':$("#from").val()})); 这里的value 封装了一个json将数据拼接后发送,后台进行解析并进行业务处理 演示效果

请添加图片描述 请添加图片描述

客户端 小小 和 大大准备进行连接 连接成功后 打印对应的sessionId,业务处理时,sessionId用于唯一标识 请添加图片描述 连接后进行消息发送,此时可以看到 大大这边可以接收到消息。 请添加图片描述 请添加图片描述 此时后台打印的日志 请添加图片描述 可以看到消息体的相关内容都捕获到,可以进行后续的业务处理。

至此,WebSocket基础开发完毕

下面是对应后台的拦截器,监听器等。

拦截器:

package com.example.websocket.intecepter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; import javax.servlet.http.HttpSession; import java.util.Map; /** * 功能描述:http握手拦截器,可以通过这个类的方法获取resuest,和response */ public class HttpHandShakeIntecepter implements HandshakeInterceptor{ @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { System.out.println("【握手拦截器】beforeHandshake"); if(request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request; HttpSession session = servletRequest.getServletRequest().getSession(); String sessionId = session.getId(); System.out.println("【握手拦截器】beforeHandshake sessionId="+sessionId); attributes.put("sessionId", sessionId); } return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { System.out.println("【握手拦截器】afterHandshake"); if(request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request; HttpSession session = servletRequest.getServletRequest().getSession(); String sessionId = session.getId(); System.out.println("【握手拦截器】afterHandshake sessionId="+sessionId); } } } package com.example.websocket.intecepter; import com.alibaba.fastjson.JSONObject; import com.example.websocket.Utils.Millisecond; import com.example.websocket.controller.v6.UserChatController; import com.example.websocket.model.InMessage; import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; 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.ChannelInterceptorAdapter; import java.util.Map; /** * 功能描述:频道拦截器 ,类似管道,可以获取消息的一些meta数据 */ @Slf4j public class SocketChannelIntecepter extends ChannelInterceptorAdapter{ /** * 在完成发送之后进行调用,不管是否有异常发生,一般用于资源清理 */ @Override public void afterSendCompletion(Message message, MessageChannel channel, boolean sent, Exception ex) { System.out.println("SocketChannelIntecepter->afterSendCompletion"); super.afterSendCompletion(message, channel, sent, ex); } /** * 在消息被实际发送到频道之前调用 */ @Override public Message preSend(Message message, MessageChannel channel) { System.out.println("SocketChannelIntecepter->preSend"); return super.preSend(message, channel); } /** * 发送消息调用后立即调用 */ @Override public void postSend(Message message, MessageChannel channel, boolean sent) { System.out.println("SocketChannelIntecepter->postSend"); String s = new String((byte[]) message.getPayload()); //InMessage inMessage = JSONObject.parseObject(s, new TypeReference(){}); //System.out.println(inMessage); System.out.println(s); StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);//消息头访问器 if (headerAccessor.getCommand() == null ) { return ;// 避免非stomp消息类型,例如心跳检测 } Map sessionAttributes = headerAccessor.getSessionAttributes(); System.out.println(sessionAttributes); String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString(); System.out.println("SocketChannelIntecepter -> sessionId = "+sessionId); StompCommand command = headerAccessor.getCommand(); System.out.println(headerAccessor.getCommand()); switch (headerAccessor.getCommand()) { case CONNECT: connect(sessionId); break; case DISCONNECT: disconnect(sessionId); break; case SUBSCRIBE: break; case UNSUBSCRIBE: break; default: break; } } long btime = 0; long etime = 0; //连接成功 private void connect(String sessionId){ System.out.println("连接成功 sessionId="+sessionId); System.out.println("connect sessionId="+sessionId); btime = System.currentTimeMillis(); System.out.println(System.currentTimeMillis()); } //断开连接 private void disconnect(String sessionId){ System.out.println("断开连接 sessionId="+sessionId); System.out.println("disconnect sessionId="+sessionId); //用户下线操作 UserChatController.onlineUser.remove(sessionId); etime = System.currentTimeMillis(); long difference = Millisecond.getDifference(btime, etime, 0); System.out.println("通讯时间--------->"+difference+"秒"); } }

监听器:

package com.example.websocket.listener; import org.springframework.context.ApplicationListener; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.stereotype.Component; import org.springframework.web.socket.messaging.SessionConnectEvent; @Component public class ConnectEventListener implements ApplicationListener { public void onApplicationEvent(SessionConnectEvent event) { StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); System.out.println("【ConnectEventListener监听器事件 类型】"+headerAccessor.getCommand().getMessageType()); } } package com.example.websocket.listener; import org.springframework.context.ApplicationListener; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.stereotype.Component; import org.springframework.web.socket.messaging.SessionSubscribeEvent; /** * 功能描述:springboot使用,订阅事件 */ @Component public class SubscribeEventListener implements ApplicationListener { /** * 在事件触发的时候调用这个方法 * * StompHeaderAccessor 简单消息传递协议中处理消息头的基类, * 通过这个类,可以获取消息类型(例如:发布订阅,建立连接断开连接),会话id等 * */ public void onApplicationEvent(SessionSubscribeEvent event) { StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); System.out.println("【SubscribeEventListener监听器事件 类型】"+headerAccessor.getCommand().getMessageType()); System.out.println("【SubscribeEventListener监听器事件 sessionId】"+headerAccessor.getSessionAttributes().get("sessionId")); } }


【本文地址】

公司简介

联系我们

今日新闻


点击排行

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

推荐新闻


图片新闻

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

专题文章

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