spring 实现stomp协议的websocket代理,以rabbitmq为代理服务,实现多客户端的实时通信。
核心代码
spring创建基于rabbitmq的代理通信服务。
import org.springframework.beans.factory.annotation.Autowired;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.EnableWebSocketMessageBroker;import org.springframework.web.socket.config.annotation.StompEndpointRegistry;import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Autowired private HeaderParamInterceptor headerParamInterceptor; @Autowired private OutInterceptor outInterceptor;
//创建websocket接口.解决跨域
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws") .setAllowedOriginPatterns("*") .withSockJS();//定义前端采用socketjs实现,若前后端通过nginx代理,需要注掉。 } @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(headerParamInterceptor); } @Override public void configureClientOutboundChannel(ChannelRegistration registration) { registration.interceptors(outInterceptor); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { //destination 目的地址 的前缀,下面会针对不同的前缀进行实际代码配置 registry.enableStompBrokerRelay("/topic") .setAutoStartup(true) .setRelayHost("192.168.20.211") .setRelayPort(61613) .setVirtualHost("/") .setClientLogin("xx") .setClientPasscode("xxxxx) .setSystemLogin("xx") .setSystemPasscode("xxxxx") .setSystemHeartbeatReceiveInterval(5000) .setSystemHeartbeatSendInterval(4000); }}
js核心代码
let socket = new SockJS('http://localhost:8090/ws');stompClient = Stomp.over(socket);stompClient.connect({username: username}, onConnected, onError);
function onError(error) { console.log(error)}function onError(error) { console.log(error)}function onConnected() { //MQ代理 enableStompBrokerRelay destinationPrefixes 为 /exchange // stompClient.subscribe('/exchange/customer.exchange/customer.exchange.routkey', function (payload) { // // console.log('/exchange/customer.exchange/customer.exchange.routkey', payload) // // stompClient.send("/exchange/customer.exchange/customer.exchange.response", {}, JSON.stringify({ // "key":"/exchange/customer.exchange/customer.exchange.routkey" // })); // }); // stompClient.subscribe('/exchange/amq.topic/default.topic.exchange.routkey', function (payload) { // console.log('/exchange/amq.topic/default.topic.exchange.routkey', payload) // // stompClient.send("/exchange/amq.topic/default.topic.exchange.response", {}, JSON.stringify({ // "key":"/exchange/amq.topic/default.topic.exchange.response" // })); // }); // MQ代理 enableStompBrokerRelay destinationPrefixes 为 /queue /amq/queue // stompClient.subscribe('/queue/default.exchange.queue', function (payload) { // console.log('/queue/default.exchange.queue', payload) // // stompClient.send("/queue/default.exchange.response", {}, JSON.stringify({ // "key":"/queue/default.exchange.queue" // })); // }); stompClient.subscribe('/amq/queue/default.exchange.queue2', function (payload) { console.log('/amq/queue/default.exchange.queue2', payload) stompClient.send("/amq/queue/default.exchange.response2", {}, JSON.stringify({ "key":"/amq/queue/default.exchange.queue2" })); }); // // //MQ代理 enableStompBrokerRelay destinationPrefixes 为 /topic stompClient.subscribe('/topic/topic.exchange.routkey', function (payload) { console.log('/topic/topic.exchange.routkey', payload) stompClient.send("/topic/topic.exchange.routkey.response", {}, JSON.stringify({ "key":"/topic/topic.exchange.routkey" })); });}function onConnected() { //MQ代理 enableStompBrokerRelay destinationPrefixes 为 /exchange // stompClient.subscribe('/exchange/customer.exchange/customer.exchange.routkey', function (payload) { // // console.log('/exchange/customer.exchange/customer.exchange.routkey', payload) // // stompClient.send("/exchange/customer.exchange/customer.exchange.response", {}, JSON.stringify({ // "key":"/exchange/customer.exchange/customer.exchange.routkey" // })); // }); // stompClient.subscribe('/exchange/amq.topic/default.topic.exchange.routkey', function (payload) { // console.log('/exchange/amq.topic/default.topic.exchange.routkey', payload) // // stompClient.send("/exchange/amq.topic/default.topic.exchange.response", {}, JSON.stringify({ // "key":"/exchange/amq.topic/default.topic.exchange.response" // })); // }); // MQ代理 enableStompBrokerRelay destinationPrefixes 为 /queue /amq/queue // stompClient.subscribe('/queue/default.exchange.queue', function (payload) { // console.log('/queue/default.exchange.queue', payload) // // stompClient.send("/queue/default.exchange.response", {}, JSON.stringify({ // "key":"/queue/default.exchange.queue" // })); // }); stompClient.subscribe('/amq/queue/default.exchange.queue2', function (payload) { console.log('/amq/queue/default.exchange.queue2', payload) stompClient.send("/amq/queue/default.exchange.response2", {}, JSON.stringify({ "key":"/amq/queue/default.exchange.queue2" })); }); // // //MQ代理 enableStompBrokerRelay destinationPrefixes 为 /topic stompClient.subscribe('/topic/topic.exchange.routkey', function (payload) { console.log('/topic/topic.exchange.routkey', payload) stompClient.send("/topic/topic.exchange.routkey.response", {}, JSON.stringify({ "key":"/topic/topic.exchange.routkey" })); });}
采用mq作为代理服务时,代理设置不同的destinationPrefixes,针对rabbitmq的不用exchange,routkey,queue的设置。
/exchange
通过交换机订阅/发布消息,
a. /exchange:固定值
b. exchangename:交换机名称
c. [routing_key]:路由键,可选
对于接收者端,该 destination 会创建一个唯一的、自动删除的随机queue, 并根据 routing_key将该 queue 绑定到所给的 exchangename,实现对该队列的消息订阅。
对于发送者端,消息就会被发送到定义的 exchange中,并且指定了 routing_key。
spring 配置
websocketMessageBrokerConfigurer
@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) { // /exchange 指定broker响应前缀 registry.enableStompBrokerRelay("/exchange") .setAutoStartup(true) .setRelayHost("192.168.20.211") //mq 地址 .setRelayPort(61613)//mq stomp支持的端口 .setVirtualHost("/") .setClientLogin("xxxx") .setClientPasscode("xxxx") .setSystemLogin("xx") .setSystemPasscode("xxxxxxx") .setSystemHeartbeatReceiveInterval(5000) .setSystemHeartbeatSendInterval(4000);}
测试发送消息
/** * rabbbitmq * 向 默认 exchange 交换器 发送消息, * 根据routkey 路由键 创建 queue 队列, 队列名称就是路由键 * 队列属性 默认为 持久化 * * @param routKey */@GetMapping("/customertExchange")public void sendToCustomerExchage(String exchanage , String routKey){ JSONObject msg = new JSONObject(); msg.put("exchange",exchanage); msg.put("routkey",routKey); msg.put("queue",routKey); msg.put("msg","向默认exchanage 发送消息"); rabbitTemplate.convertAndSend(exchanage,routKey,msg.toJSONString());}
/** * rabbbitmq * 向 默认 exchange 交换器 发送消息, * 根据routkey 路由键 创建 queue 队列, 队列名称就是路由键 * 队列属性 默认为 持久化 * * @param routKey */@GetMapping("/defaultTopicExchange")public void sendToDefaultTopicExchage( String routKey){ JSONObject msg = new JSONObject(); msg.put("exchange","amq.topic"); msg.put("routkey",routKey); msg.put("queue",routKey); msg.put("msg","向默认exchanage 发送消息"); rabbitTemplate.convertAndSend("amq.topic",routKey,msg.toJSONString());}
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "one"), exchange = @Exchange(value = "customer.exchange",type = ExchangeTypes.TOPIC), key="customer.exchange.response"))public void handler1(String msg){ log.info("/exchange/customer.exchange/customer.exchange.response--------------------"); log.info(msg); log.info("/exchange/customer.exchange/customer.exchange.response+++++++++++++++++++++");}@RabbitListener(bindings =@QueueBinding(value = @Queue(value = "two"), exchange = @Exchange(value = "amq.topic",type = ExchangeTypes.TOPIC), key="default.topic.exchange.response"))public void handler2(String msg){ log.info("/exchange/amq.topic/default.topic.exchange.response--------------------"); log.info(msg); log.info("/exchange/amq.topic/default.topic.exchange.response+++++++++++++++++++++");
js 代码
stompClient.subscribe('/exchange/customer.exchange/customer.exchange.routkey', function (payload) { console.log('/exchange/customer.exchange/customer.exchange.routkey', payload) stompClient.send("/exchange/customer.exchange/customer.exchange.response", {}, JSON.stringify({ "key":"/exchange/customer.exchange/customer.exchange.
" }));});
stompClient.subscribe('/exchange/amq.topic/default.topic.exchange.routkey', function (payload) { console.log('/exchange/amq.topic/default.topic.exchange.routkey', payload) stompClient.send("/exchange/amq.topic/default.topic.exchange.response", {}, JSON.stringify({ "key":"/exchange/amq.topic/default.topic.exchange.response" }));});
/queue/queuename
使用 默认交换机 订阅/发布消息,默认由stomp自动创建一个持久化队列,参数说明
a. /queue:固定值
b. queuename:自动创建一个持久化队列
对于接收者端,订阅队列queuename的消息
对于接收者端,向queuename发送消息,destination 只会在第一次发送消息的时候会创建一个持久化队列。
spring 配置
websocketMessageBrokerConfigurer
@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableStompBrokerRelay("/queue") .setAutoStartup(true) .setRelayHost("192.168.20.211") .setRelayPort(61613) .setVirtualHost("/") .setClientLogin("xxx") .setClientPasscode("xxxxx") .setSystemLogin("xx") .setSystemPasscode("xxxxxx") .setSystemHeartbeatReceiveInterval(5000) .setSystemHeartbeatSendInterval(4000);}
测试发送消息
/** * rabbbitmq * 向 默认 exchange 交换器 发送消息, * 根据routkey 路由键 创建 queue 队列, 队列名称就是路由键 * 队列属性 默认为 持久化 * * @param routKey */@GetMapping("/defaultExchange")public void sendToDefaultExchage(String routKey){ JSONObject msg = new JSONObject(); msg.put("routkey",routKey); msg.put("queue",routKey); msg.put("msg","向默认exchanage 发送消息"); rabbitTemplate.convertAndSend(routKey,msg.toJSONString());}
@RabbitListener(queues = "default.exchange.response")public void handler3(String msg){ log.info("/queue/default.exchange.response--------------------"); log.info(msg); log.info("/queue/default.exchange.response+++++++++++++++++++++");}
js代码
stompClient.subscribe('/queue/default.exchange.queue', function (payload) { console.log('/queue/default.exchange.queue', payload) stompClient.send("/queue/default.exchange.response", {}, JSON.stringify({ "key":"/queue/default.exchange.queue" }));});
/amq/queue/queuename
使用 默认交换机 订阅/发布消息
a. /amq/queue:固定值
b. queuename:队列名
对于接收者端,订阅队列queuename的消息,队列不存在时,会报错。
对于接收者端,向queuename发送消息,队列不存在时,发送成功,但是不会创建队列。
与/queue/queuename的区别在于队列不由stomp自动进行创建。
spring 配置
websocketMessageBrokerConfigurer
@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableStompBrokerRelay("/amq/queue") .setAutoStartup(true) .setRelayHost("192.168.20.211") .setRelayPort(61613) .setVirtualHost("/") .setClientLogin("xx") .setClientPasscode("xxxxx.com") .setSystemLogin("xxx") .setSystemPasscode("xxxxx") .setSystemHeartbeatReceiveInterval(5000) .setSystemHeartbeatSendInterval(4000);}
测试发送消息
/** * rabbbitmq * 向 默认 exchange 交换器 发送消息, * 根据routkey 路由键 创建 queue 队列, 队列名称就是路由键 * 队列属性 默认为 持久化 * * @param routKey */@GetMapping("/defaultExchange")public void sendToDefaultExchage(String routKey){ JSONObject msg = new JSONObject(); msg.put("routkey",routKey); msg.put("queue",routKey); msg.put("msg","向默认exchanage 发送消息"); rabbitTemplate.convertAndSend(routKey,msg.toJSONString());}
@RabbitListener(queues = "default.exchange.response2")public void handler4(String msg){ log.info("/queue/default.exchange.response2--------------------"); log.info(msg); log.info("/queue/default.exchange.response2+++++++++++++++++++++");}
js代码
stompClient.subscribe('/amq/queue/default.exchange.queue2', function (payload) { console.log('/amq/queue/default.exchange.queue2', payload) stompClient.send("/amq/queue/default.exchange.response2", {}, JSON.stringify({ "key":"/amq/queue/default.exchange.queue2" }));});
/topic/routing_key
通过amq.topic交换机订阅/发布消息,订阅时默认创建一个临时队列(队列属性为自动删除),通过routing_key与topic进行绑定
a. /topic:固定前缀
b. routing_key:路由键
对于接收者端,订阅时默认创建一个临时队列,通过routing_key与topic进行绑定,接收者断开后,临时队列对自动删除。
对于发送者端,消息会被发送到 amq.topic 交换机中,不会自动创建队列。
spring 配置
websocketMessageBrokerConfigurer
@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableStompBrokerRelay("/topic") .setAutoStartup(true) .setRelayHost("192.168.20.211") .setRelayPort(61613) .setVirtualHost("/") .setClientLogin("xxx") .setClientPasscode("xxxxxxx") .setSystemLogin("xxxx") .setSystemPasscode("xxxxxx") .setSystemHeartbeatReceiveInterval(5000) .setSystemHeartbeatSendInterval(4000);}
测试发送消息
/** * rabbbitmq * 向 默认 exchange 交换器 发送消息, * 根据routkey 路由键 创建 queue 队列, 队列名称就是路由键 * 队列属性 默认为 持久化 * * @param routKey */@GetMapping("/defaultTopicExchange")public void sendToDefaultTopicExchage( String routKey){ JSONObject msg = new JSONObject(); msg.put("exchange","amq.topic"); msg.put("routkey",routKey); msg.put("queue",routKey); msg.put("msg","向默认exchanage 发送消息"); rabbitTemplate.convertAndSend("amq.topic",routKey,msg.toJSONString());}
js代码
stompClient.subscribe('/topic/topic.exchange.routkey', function (payload) { console.log('/topic/topic.exchange.routkey', payload) stompClient.send("/topic/topic.exchange.routkey.response", {}, JSON.stringify({ "key":"/topic/topic.exchange.routkey" }));});