Spring Boot 集成 websocket,使用RabbitMQ做为消息代理
<!-- rabbitmq -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- Stomp协议中使用RabbitMQ作为消息代理,所以还需要加入以下三个依赖 -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-net</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
<!-- Stomp协议中使用RabbitMQ作为消息代理,三个依赖结束 -->
4.1. /exchange/exchangename/[routing_key]
通过交换机订阅/发布消息,交换机需要手动创建,参数说明
a. /exchange:固定值
b. exchangename:交换机名称
c. [routing_key]:路由键,可选
.2. /queue/queuename
使用默认交换机订阅/发布消息,默认由stomp自动创建一个持久化队列,参数说明
a. /queue:固定值
b. queuename:自动创建一个持久化队列
对于接收者端,订阅队列queuename的消息
对于接收者端,向queuename发送消息
[对于 SEND frame,destination 只会在第一次发送消息的时候会定义的共享 queue]
4.3. /amq/queue/queuename
和上文的”/queue/queuename”相似,两者的区别是
a. 与/queue/queuename的区别在于队列不由stomp自动进行创建,队列不存在失败
这种情况下无论是发送者还是接收者都不会产生队列。 但如果该队列不存在,接收者会报错。
4.4. /topic/routing_key
通过amq.topic交换机订阅/发布消息,订阅时默认创建一个临时队列,通过routing_key与topic进行绑定
a. /topic:固定前缀
b. routing_key:路由键
对于发送者端,会创建出自动删除的、非持久的队列并根据 routing_key路由键绑定到 amq.topic 交换机 上,同时实现对该队列的订阅。
对于发送者端,消息会被发送到 amq.topic 交换机中。
@SendTo:会将接收到的消息发送到指定的路由目的地,所有订阅该消息的用户都能收到,属于广播。
@SendToUser:消息目的地有UserDestinationMessageHandler来处理,会将消息路由到发送者对应的目的地, 此外该注解还有个broadcast属性,表明是否广播。
就是当有同一个用户登录多个session时,是否都能收到。取值true/false.
后台,控制层
//单独发, 一对一聊天
@MessageMapping("/aloneRequestStomp") // 浏览器发送请求通过@messageMapping 映射/welcome 这个地址。
//@SendTo("/alone/getResponse") // 服务器端会将消息广播给所有订阅/mass/getResponse这个路径的用户。
public String alone(String message) throws Exception {
JSONObject mess= JSON.parseObject(message);
System.out.println("单独发, 一对一聊天"+message);
User user = new User();
user.setName("lmc168");
user.setAge(20);
user.setUserId(mess.getString("userId"));
this.template.convertAndSendToUser(user.getUserId()+"","/alone/getResponse",mess);
return message;
}
前端代码
<!DOCTYPE html>
<html>
<head>
<title>websocket.html</title>
<meta name="keywords" content="keyword1,keyword2,keyword3">
<meta name="description" content="this is my page">
<meta name="content-type" content="text/html" charset="UTF-8">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
<!-- 独立css -->
<link href="/css/plugins/toastr/toastr.min.css" rel="stylesheet">
</head>
<body>
<div>
<p id="response"></p>
<p id="id_test">触发打印消息</p>
</div>
<!-- 独立JS -->
<script type="text/javascript" src="/js/jquery.min.js?v=2.1.4" charset="utf-8"></script>
<script type="text/javascript" src="/js/webSocket/sockjs.min.js" charset="utf-8"></script>
<script type="text/javascript" src="/js/webSocket/stomp.min.js" charset="utf-8"></script>
<script src="/js/toastr/toastr.min.js"></script>
<!-- <script type="text/javascript" src="/js/webSocket/stomp.js" charset="utf-8"></script> -->
</body>
<script type="text/javascript">
var stompClient = null;
//加载完浏览器后 调用connect(),打开双通道
$(function(){
//打开双通道
connect();
})
//强制关闭浏览器 调用websocket.close(),进行正常关闭
window.onunload = function() {
disconnect()
}
function connect(){
var socket = new SockJS('http://127.0.0.1:5080/endpointOyzc'); //连接SockJS的endpoint名称为"endpointOyzc"
stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端
stompClient.connect({},function(frame){//连接WebSocket服务端
console.log('Connected:' + frame);
// stompClient.send("/app/marco",{},'向服务端发送消息'); //后台需要 @MessageMapping("/marco")
//通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息
stompClient.subscribe('/topic/getResponse',function(response){
showResponse(JSON.parse(response.body));
}
//关闭双通道
function disconnect(){
if(stompClient != null) {
stompClient.disconnect();
}
console.log("Disconnected");
}
function showResponse(message){
var response = $("#response");
response.append("<p>name: "+message.name+" 后台推送的数字 "+message.age+"</p>");
}
</script>
</html>
配置文件
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//注册两个STOMP的endpoint,分别用于广播和点对点 ,并指定 SockJS协议。客户端就可以通过这个端点来进行连接;withSockJS作用是添加SockJS支持
registry.addEndpoint("/endpointUser").withSockJS(); //指定谁能够收到令牌
registry.addEndpoint("/endpointOyzc").setAllowedOrigins("*").withSockJS(); setAllowedOrigins 添加允许跨域访问
//为/endpointOyzc路径启用SockJS功能
// registry.addInterceptors(HandshakeInterceptor()) // 添加自定义拦截
// registry.addEndpoint("/chatwebsocket").setAllowedOrigins("*").setHandshakeHandler(new MyHandsHandler()).withSockJS();
registry.addEndpoint("/chatwebsocket").setAllowedOrigins("*").withSockJS();//使用配置的拦截方法configureWebSocketTransport
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 使用RabbitMQ做为消息代理,替换默认的Simple Broker
//定义了服务端接收地址的前缀,也即客户端给服务端发消息的地址前缀,@SendTo(XXX) 也可以重定向
// registry.setApplicationDestinationPrefixes("/app")
// "STOMP broker relay"处理所有消息将消息发送到外部的消息代理
registry.enableStompBrokerRelay("/exchange","/topic","/queue","/amq/queue")
.setVirtualHost(rabbitProperties.getVirtualHost())
.setRelayHost(rabbitProperties.getHost())
.setClientLogin(rabbitProperties.getUsername())
.setClientPasscode(rabbitProperties.getPassword())
.setSystemLogin(rabbitProperties.getUsername())
.setSystemPasscode(rabbitProperties.getPassword())
.setSystemHeartbeatSendInterval(5000)
.setSystemHeartbeatReceiveInterval(4000);
}
代码比较乱,还是在github上看吧,