温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

SpringBoot+STOMP协议怎么实现私聊、群聊功能

发布时间:2021-06-11 09:57:22 来源:亿速云 阅读:395 作者:小新 栏目:开发技术

这篇文章主要介绍了SpringBoot+STOMP协议怎么实现私聊、群聊功能,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

一、为什么需要STOMP?

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

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

二、STOMP详解

SpringBoot+STOMP协议怎么实现私聊、群聊功能

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

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

  • STOMP 服务器:ActiveMQ、RabbitMQ、StompServer、…

  • STOMP 客户端库:stomp.js(javascript)

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

  • CONNECT 启动与服务器的流或 TCP 连接

  • SEND发送消息

  • SUBSCRIBE 订阅主题

  • UNSUBSCRIBE 取消订阅

  • BEGIN 启动事物

  • COMMIT提交事物

  • ABORT回滚事物

  • ACK确认来自订阅的消息的消费

  • NACK告诉服务器客户端没有消费该消息

  • DISCONNECT断开连接

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

STOMP协议官方文档

三、SpringBoot集成STOMP代码示例

3.1、功能示例

SpringBoot+STOMP协议怎么实现私聊、群聊功能 

SpringBoot+STOMP协议怎么实现私聊、群聊功能 

3.2、架构图

SpringBoot+STOMP协议怎么实现私聊、群聊功能

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()) + "!"));	} }
  • @MessageMapping 功能与RequestMapping注解类似。send指令发送信息时添加此注解

  • @SendTo/@SendToUser 将信息输出到该主题。客户端订阅同样的主题后就会收到信息。

  • 在只有指定@MessageMapping@MessageMapping == “/topic” + @SendTo

  • 如果想使用rest接口发送消息。可以通过SimpMessagingTemplate进行发送。

  • 点对点聊天时,可以使用SimpMessagingTemplate.convertAndSendToUser方法发送。个人意味比注解@SendToUser更加容易理解,更加方便

  • convertAndSendToUser方法和convertAndSend类似,区别在于convertAndSendToUser方法会在主题默认添加/user/为前缀。因此,示例代码中convertAndSend方法直接传入"/topic/user/"+message.getUserId ()+"/sendToUser" 也是点对点发送。topic其中是默认前缀。

  • 如果想修改convertAndSendToUser默认前缀可在配置类进行配置,可在WebSocketConfig类中查看。

3.4、h6代码

<!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://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></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><h3 >Seems your browser doesn't support Javascript! Websocket relies on Javascript being     enabled. Please enable     Javascript and reload this page!</h3></noscript> <div id="main-content" class="container">     <div class="row">         <div class="col-md-6">             <form class="form-inline">                 <div 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>                 </div>             </form>         </div>         <div class="col-md-6">             <form class="form-inline">                 <div 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">                 </div>                 <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>         </div>     </div>     <div class="row">         <div class="col-md-12">             <table id="conversation" class="table table-striped">                 <thead>                 <tr>                     <th>Greetings</th>                 </tr>                 </thead>                 <tbody id="greetings">                 </tbody>             </table>         </div>     </div> </div> </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(); }); });

感谢你能够认真阅读完这篇文章,希望小编分享的“SpringBoot+STOMP协议怎么实现私聊、群聊功能”这篇文章对大家有帮助,同时也希望大家多多支持亿速云,关注亿速云行业资讯频道,更多相关知识等着你来学习!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI