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

网友投稿 480 2022-10-20


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

一、为什么需要STOMP?

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

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

二、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、功能示例

3.2、架构图

3.3、服务端代码

pom文件引入jar

xmlns:xsi="http://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">

4.0.0

org.example

websocket-demo

1.0-SNAPSHOT

org.springframework.boot

spring-boot-starter-parent

2.3.10.RELEASE

1.8

org.springframework.boot

spring-boot-starter-web

org.apache.commons

commons-lang3

org.projectlombok

lombok

1.18.12

org.springframework.boot

spring-boot-starter-websocket

org.webjars

webjars-locator-core

org.webjars

sockjs-client

1.0.2

org.webjars

stomp-websocket

2.3.3

org.webjars

bootstrap

3.3.7

org.webjars

jquery

3.1.0

com.alibaba

fastjson

1.2.62

org.apache.maven.plugins

maven-compiler-plugin

1.8

1.8

xmlns:xsi="http://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">

4.0.0

org.example

websocket-demo

1.0-SNAPSHOT

org.springframework.boot

spring-boot-starter-parent

2.3.10.RELEASE

1.8

org.springframework.boot

spring-boot-starter-web

org.apache.commons

commons-lang3

org.projectlombok

lombok

1.18.12

org.springframework.boot

spring-boot-starter-websocket

org.webjars

webjars-locator-core

org.webjars

sockjs-client

1.0.2

org.webjars

stomp-websocket

2.3.3

org.webjars

bootstrap

3.3.7

org.webjars

jquery

3.1.0

com.alibaba

fastjson

1.2.62

org.apache.maven.plugins

maven-compiler-plugin

1.8

1.8

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、h5代码

Seems your browser doesn't support Javascript! Websocket relies on Javascript being

enabled. Please enable

Javascript and reload this page!

Greetings

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("

}

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参考文档


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:数据中心综合布线典型产品选择
下一篇:《数据中心布线系统的设计与施工技术白皮书》目录
相关文章

 发表评论

暂时没有评论,来抢沙发吧~