SpringBoot+STOMP协议实现私聊、群聊

一、为什么需要STOMP?

        WebSocket 协议是一种相当低级的协议。它定义了如何将字节流转换为帧。帧可以包含文本或二进制消息。由于消息本身不提供有关如何路由或处理它的任何其他信息,因此很难在不编写其他代码的情况下实现更复杂的应用程序。幸运的是,WebSocket 规范允许在更高的应用程序级别上使用子协议。

        另外,单单使用WebSocket完成群聊、私聊功能时,需要自己管理session信息,通过STOMP协议时,spring已经封装好,开发者只需要关注自己的主题、订阅关系即可。

二、STOMP详解

        STOMP 中文为“面向消息的简单文本协议”,STOMP 提供了能够协作的报文格式,以至于 STOMP 客户端可以与任何 STOMP 消息代理(Brokers)进行通信,从而为多语言,多平台和 Brokers 集群提供简单且普遍的消息协作。STOMP 协议可以建立在 WebSocket 之上,也可以建立在其他应用层协议之上。通过 Websocket建立 STOMP 连接,也就是说在 Websocket 连接的基础上再建立 STOMP 连接。最终实现如上图所示,这一点可以在代码中有一个良好的体现。

业界已经有很多优秀的 STOMP 的服务器/客户端的开源实现

        Stomp 的特点是客户端的实现很容易,服务端相当于消息队列的 broker 或者是 server,一般不需要我们去实现,所以重点关注一下客户端如何使用

        其实STOMP协议并不是为WS所设计的, 它其实是消息队列的一种协议, 和AMQP,JMS是平级的。 只不过由于它的简单性恰巧可以用于定义WS的消息体格式。 目前很多服务端消息队列都已经支持了STOMP, 比如RabbitMQ, Apache ActiveMQ等。很多语言也都有STOMP协议的客户端解析库,像JAVA的Gozirra,C的libstomp,Python的pyactivemq,JavaScript的stomp.js等等。

STOMP协议官方文档

三、SpringBoot集成STOMP代码示例

3.1、功能示例

 

 

3.2、架构图

3.3、服务端代码

pom文件引入jar

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>websocket-demo</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.10.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator-core</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>sockjs-client</artifactId> <version>1.0.2</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>stomp-websocket</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.7</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>

WebSocketMessageBroker配置类

@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { // 启用一个简单的基于内存的消息代理 @Override public void configureMessageBroker(MessageBrokerRegistry config) { //通过/topic 开头的主题可以进行订阅 config.enableSimpleBroker("/topic"); //send命令时需要带上/app前缀 config.setApplicationDestinationPrefixes("/app"); //修改convertAndSendToUser方法前缀, 稍后解释作用 // config.setUserDestinationPrefix ("/myUserPrefix"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //连接前缀 registry.addEndpoint("/gs-guide-websocket") .setAllowedOrigins("*") // 跨域处理 .withSockJS(); //支持socketJs } }

@EnableWebSocketMessageBroker注解启用 WebSocket 消息处理,由消息代理支持。

SockJS 有一些浏览器中缺少对 WebSocket 的支持,而 SockJS 是一个浏览器的 JavaScript库,它提供了一个类似于网络的对象,SockJS 提供了一个连贯的,跨浏览器的JavaScriptAPI,它在浏览器和 Web 服务器之间创建了一个低延迟、全双工、跨域通信通道。SockJS 的一大好处在于提供了浏览器兼容性。即优先使用原生WebSocket,如果浏览器不支持 WebSocket,会自动降为轮询的方式。如果你使用 Java 做服务端,同时又恰好使用 Spring Framework 作为框架,那么推荐使用SockJS。

控制器代码

@Slf4j @RestController public class TestController { @Autowired private SimpMessagingTemplate simpMessagingTemplate; @MessageMapping("/hello") @SendTo ("/topic/greetings") public Greeting greeting(HelloMessage message) throws Exception { Thread.sleep(1000); // simulated delay return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); } @MessageMapping("/topic/greetings") public Greeting greeting2(HelloMessage message) throws Exception { Thread.sleep(1000); // simulated delay log.info ("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); } @GetMapping ("/hello2") public void greeting3(HelloMessage message) throws Exception { Thread.sleep(1000); // simulated delay simpMessagingTemplate.convertAndSend ("/topic/greetings", new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!")); } @MessageMapping("/sendToUser") public void sendToUser(HelloMessage message) throws Exception { Thread.sleep(1000); // simulated delay log.info ("userId:{},msg:{}",message.getUserId (),message.getName ()); // simpMessagingTemplate.convertAndSendToUser (message.getUserId (),"/sendToUser", // new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!")); // simpMessagingTemplate.convertAndSend ("/user/1/sendToUser", // new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!")); simpMessagingTemplate.convertAndSend ("/topic/user/"+message.getUserId ()+"/sendToUser", new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!")); } }

3.4、h5代码

<!DOCTYPE html> <html> <head> <title>Hello WebSocket</title> <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="external nofollow" rel="stylesheet"> <link href="/main.css" rel="external nofollow" rel="stylesheet"> <script type="text/javascript" src="https://mip.qiaqa.com/uploads/allimg/210611/202A622D-4.jpg"></script> <script src="/webjars/sockjs-client/sockjs.min.js"></script> <script src="/webjars/stomp-websocket/stomp.min.js"></script> <script src="/app.js"></script> </head> <body> <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being enabled. Please enable Javascript and reload this page!</h2></noscript> <p id="main-content" class="container"> <p class="row"> <p class="col-md-6"> <form class="form-inline"> <p class="form-group"> <label for="connect">WebSocket connection:</label> <button id="connect" class="btn btn-default" type="submit">Connect</button> <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect </button> </p> </form> </p> <p class="col-md-6"> <form class="form-inline"> <p class="form-group"> <label for="name">What is your name?</label> <input type="text" id="name" class="form-control" placeholder="Your name here..."> <input type="text" id="userId" class="form-control" placeholder="userId"> </p> <button id="send" class="btn btn-default" type="submit">Send</button> <button id="send2" class="btn btn-default" type="submit">Send2</button> <button id="send3" class="btn btn-default" type="submit">SendToUser</button> </form> </p> </p> <p class="row"> <p class="col-md-12"> <table id="conversation" class="table table-striped"> <thead> <tr> <th>Greetings</th> </tr> </thead> <tbody id="greetings"> </tbody> </table> </p> </p> </p> </body> </html>

app.js

var stompClient = null; var userId = null; function setConnected(connected) { $("#connect").prop("disabled", connected); $("#disconnect").prop("disabled", !connected); if (connected) { $("#conversation").show(); } else { $("#conversation").hide(); } $("#greetings").html(""); } function connect() { var socket = new SockJS('/gs-guide-websocket'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { setConnected(true); console.log('Connected: ' + frame); stompClient.subscribe('/topic/greetings', function (greeting) { showGreeting(JSON.parse(greeting.body).content); }); //对应controller greeting2方法 注意,这儿有两个topic stompClient.subscribe('/topic/topic/greetings', function (greeting) { showGreeting(JSON.parse(greeting.body).content); }); stompClient.subscribe('/topic/user/'+userId+'/sendToUser', function (greeting) { showGreeting(JSON.parse(greeting.body).content); }); stompClient.subscribe('/user/'+userId+'/sendToUser', function (greeting) { showGreeting(JSON.parse(greeting.body).content); }); }); } function disconnect() { if (stompClient !== null) { stompClient.disconnect(); } setConnected(false); console.log("Disconnected"); } function sendName() { stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()})); // stompClient.send("/hello", {}, JSON.stringify({'name': $("#name").val()})); } function sendName2() { stompClient.send("/app/topic/greetings", {}, JSON.stringify({'name': $("#name").val()})); // stompClient.send("/topic/greetings", {}, JSON.stringify({'name': $("#name").val()})); } function sendName3() { stompClient.send("/app/sendToUser", {}, JSON.stringify({'userId':$("#userId").val(),'name': $("#name").val()})); } function showGreeting(message) { $("#greetings").append("<tr><td>" + message + "</td></tr>"); } function GetQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); var r = window.location.search.substr(1).match(reg); //获取url中"?"符后的字符串并正则匹配 var context = ""; if (r != null) context = r[2]; reg = null; r = null; return context == null || context == "" || context == "undefined" ? "" : context; } $(function () { userId = GetQueryString("userId"); $("form").on('submit', function (e) { e.preventDefault(); }); $( "#connect" ).click(function() { connect(); }); $( "#disconnect" ).click(function() { disconnect(); }); $( "#send" ).click(function() { sendName(); }); $( "#send2" ).click(function() { sendName2(); }); $( "#send3" ).click(function() { sendName3(); }); });

一些无关紧要的类

public class Greeting { private String content; public Greeting() { } public Greeting(String content) { this.content = content; } public String getContent() { return content; } }

public class HelloMessage { private String userId; private String name; // 省去get/set } Name3(); }); });

spring参考文档

websocket参考文档

到此这篇关于SpringBoot+STOMP协议实现私聊、群聊的文章就介绍到这了,更多相关SpringBoot STOMP私聊、群聊内容请搜索趣讯吧以前的文章或继续浏览下面的相关文章希望大家以后多多支持趣讯吧!

发布于 2021-06-11 20:26:56
收藏
分享
海报
0 条评论
160
上一篇:你知道在Java中Integer和int的这些区别吗? 下一篇:你喜欢篮球吗?Python实现篮球游戏
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码