公司消息推送服务以RabbitMQ为基础,pc端消息推送存在消息客户端接口不统一等诸多问题,访问权限不够灵活统一等问题。
为了将公司各个应用之间进行消息解耦,对业务的透明化处理及技术架构的统一管理,降低对各业务模块对消息模块开发难度,保障消息推送服务器与业务系统的稳定性,也方便各应用的消息中间件的快速搭建,尤其对pc端直接操作消息服务器,提供可行性解决方案
即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。
stomp代理中会将stomp消息的处理委托给一个真正的消息代理(RabbitMQ,activeMQ)进行处理
可以理解为 HTML5 WebSocket
与 STOMP
协议间的桥接,目的也是为了让浏览器能够使用 RabbitMQ
。当 RabbitMQ
消息服务器开启了 STOMP 和 Web STOMP 插件后,浏览器端就可以轻松地使用 WebSocket 或者 SockerJS 客户端实现与 RabbitMQ 服务器进行通信。RabbitMQ Web STOMP
是对 STOMP
协议的桥接,因此其语法也完全遵循 STOMP
协议。STOMP
是基于 frame
的协议,与 HTTP
的frame
相似。一个 Frame 包含一个 command,一系列可选的 headers 和一个 body。STOMP client 的用户代理可以充当两个角色,当然也可能同时充当:作为生产者,通过 SEND frame 发送消息到服务器;作为消费者,发送 SUBCRIBE frame 到目的地并且通过 MESSAGE frame 从服务器获取消息。
在Web页面中利用WebSocket使用STOMP协议只需要下载stomp.js即可,考虑到老版本的浏览器不支持 WebSocket,SockJS 则提供了 WebSocket 的模拟支持。
CONNECT
与服务器建立连接,校验秘钥CONNECTED
连接成功SEND
发送消息SUBSCRIBE
订阅消息UNSUBSRIBE
取消订阅ACK
消息确认,默认为ture,自动确认,手动确认改为falseNACK
NACK是ACK的反向BEGIN
事务开始COMMIT
事务提交ABORT
事务回滚DISCONNECT
断开连接MESSAGE
用于传输从服务端订阅的消息到客户端,MESSAGE
中必须包含destionation
头,用以表示这个消息应该发送的目标。如果这个消息被使用STOMP发 送,那么这个destionation
应该与相应的SEND帧
中的目标一样ERROR
如果连接过程中出现什么错误,服务端就会发送ERROR
,并断开连接
docker run -it -d --name=rabbit-3.8 -v /d/docker/rabbitmq-stomp/conf:/etc/rabbitmq -p 5617:5617 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 -p 15670:15670 -p 15674:15674 rabbitmq:3.8.27
如果在docker 环境下 进入docker-rabbitmq容器内 执行
rabbitmq-plugins enable rabbitmq_web_stomp rabbitmq-plugins enable rabbitmq_stomp
rabbitmq-plugins list
官方自带demo
rabbitmq-plugins enable rabbitmq_web_stomp_examples
如果不想用户名和密码暴露到前端,可以在配置文件中设置默认密码
stomp.default_user = admin stomp.default_pass = a123456 #更改虚拟vhost stomp.default_vhost = / #web stomp 端口修改 web_stomp.tcp.port = 12345 #是否传输压缩 web_stomp.ws_opts.compress = true #WebSocket 超时时间 web_stomp.ws_opts.idle_timeout = 60000 #无缓存模式 #auth_backends.1 = http #混合模式 使用模块名称而不是短别名,“http” #auth_backends.1 = rabbit_auth_backend_http #auth_backends.2 = internal #内部身份验证,http授权 #auth_backends.1.authn = internal #auth_backends.1.authz = rabbit_auth_backend_http #http授权,内部身份验证 #auth_backends.1.authn = rabbit_auth_backend_http #auth_backends.1.authz = internal #stomp.default_user = James #stomp.default_pass = a123456
stomp规范没有规定broker必须支持的destination
地类型,而是在send
和message
帧中的destination
头的值是特定broker。RabbitMQ STOMP适配器支持许多不同的destination
类型
有效destination /temp-queue, /exchange, /topic, /queue, /amq/queue, /reply-queue/
SEND
到任意路由键并SUBSCRIBE
任意绑定模式; amq.direct |
direct |
D |
||
amq.fanout |
fanout |
D |
||
amq.headers |
headers |
D |
||
amq.match |
headers |
D |
||
amq.topic |
① stomp网关:就是在stomp协议基础上,创建队列,发布或订阅消息,如果非stomp网关,则无法创建队列,只能订阅已存在的队列
对应模式在stomp协议下的调用方式
//只有一个订阅者 client.subscribe("/queue/task_queue", function(data) { var msg = data.body; console.log("收到数据:"); console.log(msg); },{ack:'client'});
//过个订阅者轮询获取 client.subscribe("/queue/task_queue", function(data) { var msg = data.body; console.log("收到数据:"); console.log(msg); data.ack(); //如果后面带了参数 ack 就是指定要手动确认消息,没带就是自动确认 },{ack:'client'});
扇形交换机效率最好的,它不需要路由绑定等复杂操作
var headers = {ack: 'client'}; 交换机/交换机名 client.subscribe("/exchange/logs", function(data) { var msg = data.body; console.log("收到数据:"); console.log(msg); data.ack(); //如果后面带了参数 ack 就是指定要手动确认消息,没带就是自动确认 },headers);
var headers = {ack: 'client'}; 交换机/交换机名/routingkey client.subscribe("/exchange/amq.direct/www", function(data) { var msg = data.body; console.log("收到数据:"); console.log(msg); data.ack(); //如果后面带了参数 ack 就是指定要手动确认消息,没带就是自动确认 },headers);
function on_connect(x) { var headers = {ack: 'client'}; client.subscribe("/exchange/xxp/fast.*.*", function(data) { var msg = data.body; console.log("收到数据:"); console.log(msg); data.ack(); //如果后面带了参数 ack 就是指定要手动确认消息,没带就是自动确认 },headers); };
client.send("/queue/user1", {}, content);
提示:前端代码演示 实现连接,接受消息,发送消息等具体功能,仅对JS代码做了简单分装以及断开自动重连
stompClient.connect('shop','eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ4eHAiLCJpYXQiOiIyMDIyLTAxLTI4IDAwOjAwOjAwIiwiZXhwIjoiMjAyMi0wMS0zMSAwMDowMDowMCIsImF1ZCI6ImZyb20iLCJzdWIiOiJ0byIsInVzZXJuYW1lIjoieHhwIn0.J4fENLsLdHcTEGm6nr5AeQrsEnflVFRGItuBGqgZs1w', function (frame) { writeToScreen("connected: " + frame); console.log("连接成功") }, function (error) { console.log("连接失败"); }, '/' )
var on_connect = function(x) { //subscribe 订阅消息,名为test的队列 console.log(d.body); };
var on_error = function(f) { console.log('error'+f); };
var subscribeHeader = { "ack" : "client-individual", 'auto-delete': true } stompClient.subscribe('/queue/15029149799', function (response) { writeToScreen(response.body); response.ack(); },subscribeHeader);
var subscribeHeader = { "ack" : "client-individual", }
response.ack();
需要提前创建 交换机为 chat2,direct类型
js代码
var stompClient = null; var wsCreateHandler = null; var userId = null; var ipPort = null; function connect() { userId = GetQueryString("userId"); if(userId.replace(/(^s*)|(s*$)/g, "").length ==0){ alert("请在URL带上你的参数"); return; } var socket = new WebSocket(ipPort); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { writeToScreen("connected: " + frame); stompClient.subscribe('/exchange/chat2/'+userId, function (response) { writeToScreen('>'+response.body); }); }, function (error) { wsCreateHandler && clearTimeout(wsCreateHandler); wsCreateHandler = setTimeout(function () { console.log("重连..."); connect(); }, 10000); } ) } function disconnect() { if (stompClient != null) { stompClient.disconnect(); } writeToScreen("disconnected"); } function writeToScreen(message) { if(DEBUG_FLAG) { $("#debuggerInfo").val($("#debuggerInfo").val() + "\n" + message); } } function GetQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); var r = window.location.search.substr(1).match(reg); var context = ""; if (r != null) context = r[2]; reg = null; r = null; return context == null || context == "" || context == "undefined" ? "" : context; }
html代码
websocket View URL参数:userId=xxx UserId我的会员ID用户:消息: