使用Springboot开发websocket程序(四)——使用RabbitMQ作为STOMP消息代理

上一篇,我们在介绍了Spring中如何使用websocket的子协议stomp,并使用简单的基于内存的stomp消息代理来编写了一个web聊天室实例。基于内存的stomp消息代理,虽然能够满足基本需求,但还是存在一些不足,比如由于stomp代理在应用内部,多个外部websocket应用需要消息互通,那么就难以满足了。在本篇,我们来学习如何使用RabbitMQ作为stomp代理。 1. 为何要使用外部消息代理 简单消息代理,能够满足单websocket应用的需要,但是如果有多个websocket应用,他们之间需要进行消息共享,那么就需要做大量的工作才能实现了。其实,MQ一个最重要的作用就在于能个在各个系统间解耦。引入外部MQ作为stomp消息代理,很好的解决了多系统消息共享的问题,只要其支持stomp协议。RabbitMQ本身提供了对STOMP的支持,加上后结构变化如下: 前边的是单应用时的结构,后边为怎么了RabbitMQ过后,多个应用程序结构。 2. RabbitMQ对STOMP的支持 RabbitMQ对stomp协议的支持是通过插件的方式,默认stomp插件是关闭的,我们需要先启用之。 2.1. 启用插件 进入rabbitmq所在服务器,然后控制台输入如下命令来启用stomp插件: ``rabbitmq-plugins enable rabbitmq_stomp`` 然后可以查看插件是否启用成功: ``rabbitmq-plugins list`` 2.2. 插件配置 默认情况下,STOMP将会监听61613端口,默认的用户名和密码都为guest。通过配置文件来配置: ubuntu下rabbitmq的配置文件在/etc/rabbitmq/rabbitmq.conf,找到stomp开头的选项,就可以进行配置了 比如配置STOMP监听端口: ``stomp.listeners.tcp.1 = 12345`` RabbitMQ中STOMP适配器连接时如果用户名和密码使用默认的guest/guest,则可以忽略,如果需要修改,则配置如下: stomp.default_user = guest stomp.default_pass = guest 2.3. Destinations STOMP规范并没有规定消息代理来支持什么样的目的地(destination),只是根据消息头的destination的值来判断消息发送的目的地,一般由消息代理自定义支持,RabbitMQ中定义了几种destination类型: #exchange[/exchange]: 发送到任意的routing key和订阅任意的binding key #queue[/queue]: 发送和订阅队列,该队列由STOMP管理 #amqqueue[/amq/queue]: 发送和订阅外部创建的队列 #topic[/topic]: 发送和订阅到topic #temptopic[/temp-queue/]: 创建临时的队列(使用reply-to请求头) 现在,我们结合代码来看看Spring中对RabbitMQ的这几类destination是如何支持的。 3. Spring中使用RabbitMQ消息代理 我们通过一个demo来看看如何在Spring中使用RabbitMQ支持的这几个destination,整体界面如下; 下边的示例仅贴上部分关键代码,完整的代码可以参看文末的源码。 首先,我们创建一个名为03-websocket-stomp-rabbitmq的springboot工程,引入如下依赖: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-net</artifactId> <version>2.0.5.RELEASE</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.0.33.Final</version> </dependency> <dependencies> ...

2019-11-12 · 3 min · 509 words · Hank

使用Springboot开发websocket程序(三)——基于子协议STOMP的web聊天室

我们使用Spring Boot对原生WebSocket的支持来编写了一个简单的聊天室程序。它仅支持群发,如果要做到点对点消息,我们还需要编写代码自己实现。本篇,我们将使用websocket的子协议STOMP来更新我们的聊天室程序,使其可以支持单聊。 1. STOMP 1.1. 为什么要支持子协议 WebSocket是一种简单的通信协议,而并非消息协议。它是TCP之上的非常薄的一层,所做的事情仅仅是将字节流转换为消息流(文本或二进制)。至于消息是什么含义,怎么路由和解析,都交由应用程序自身来决定。HTTP协议是一种应用级协议,它明确告诉我们请求的地址、格式、编码等等信息,但是websocket与之不同,websocket并不提供详细的信息来告诉我们如何路由和处理消息。因此,对于开发大型应用程序而言,websocke级别太低了,要实现非常复杂的功能,我们需要进行大量的编码工作。这就好比,大多JAVA WEB开发都会选择使用Spring框架,而不是基于Servlet API来实现。 基于这个原因,WebSocket RFC定义了 子协议的使用规范。在握手阶段,客户端和服务端使用Sec-WebSocket-Protocol请求头来通知彼此使用子协议,即更高级的、应用级的协议。当然,也可以不使用子协议,但是客户端和服务端仍然需要定义消息的格式。使用更规范的通用消息协议,更能让应用程序开发和维护变得简单。STOMP就是这样的一个消息协议,Spring框架提供了对其的支持。 1.2. 什么是STOMP STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,中文称简单(流)文本定向消息协议,它是一种简单的互操作协议**,旨在通过消息代理在客户端之间传递异步消息,STOMP协议具有简单行和互操作性,因此在多种语言和多种平台上得到广泛地应用。。 STOMP是由于需要通过脚本语言(例如Ruby,Python和Perl)连接到企业消息代理而产生的,旨在对消息进行简单的消息处理,例如可靠地发送单个消息并断开连接或在给定目的地上消耗所有消息等等。它是其他开放消息协议(例如AMQP)和JMS代理(例如OpenWire)中使用的实现特定wire protocol的替代。STOMP仅仅实现部分常用的消息API,而不是实现完整的消息API,因此更加轻量和简单。 STOMP除了支持文本消息外,也支持二进制消息,默认使用UTF-8的编码格式。 STOMP有几个比较重要的概念: frame:帧,即STOMP客户端和服务端发送的数据 COMMAND: frame的命令,由STOMP规范定义,用来表示消息的特定用途,比如连接服务端、发送消息等,每个frame都有命令 destination:消息发送的目的地址,通过消息头来表示,如destination:/topic/chat STOMP基于frame,frame的模型源于HTTP,一个frame由命令、一组可选的标头和可选的主体组成,客户端和服务端发送frame来进行通信。frame的格式如下: COMMAND header1:value1 header2:value2 Body^@ frame分为两个部分:消息头和消息体。消息头第一行为命令,然后跟key:value格式的头信息;然后是消息体,消息体跟消息头之间用一个空行分隔。最后,消息体后跟八位空字节(上文用^@表示)。另外,frame的命令和消息头信息都区分大小写。 STOMP客户端支持的命令包括:SEND、SUBSCRIBE、UNSUBSCRIBE、BEGIN、COMMIT、ABORT、ACK、NACK、DISCONNECT、CONNECT、STOMP 服务端支持的命令包括:CONNECTED、MESSAGE、RECEIPT、ERROR 只有几种消息可以包含消息体:SEND、MESSAGE、ERROR 举个例子,客户端连接到服务端,则会想服务端发送如下的frame: CONNECT accept-version:1.0,1.1,2.0 host:stomp.github.org ^@ 服务端接收连接请求则会发送下边的frame给客户端,否则发送ERROR frame: CONNECTED version:1.2 ^@ 1.3. 使用STOMP的优点 使用STOMP作为子协议,与使用原生WebSocket相比,Spring框架和Spring Security可以提供更丰富的编程模型。具体的优点有以下几点: 无需自定义消息协议和消息格式 可以使用STOMP客户端,包括Spring框架中的Java客户端 消息代理(例如RabbitMQ,ActiveMQ和其他代理)可以用于管理订阅和广播消息 可以在任意数量的@Controller中组织应用程序逻辑,并根据STOMP消息头将消息路由给它们,而对于给定的连接,可以使用单个WebSocketHandler处理原始WebSocket消息 可以使用Spring Security基于STOMP 目标(destinations)和消息类型来对消息进行安全处理 详细内容请看STOMP规范: https://stomp.github.io/stomp-specification-1.2.html 2. Spring Boot中使用STOMP 使用STOMP有如此多的好处,我们看看Spring中如何使用。 在Spring boot中使用STOMP,大概需要以下几步: ...

2019-11-02 · 5 min · 914 words · Hank

使用Springboot开发websocket程序(二)——基于原生websocket的web聊天室

上一篇介绍了什么是websocket,说到websocket是一种由HTML5定义的浏览器和服务器保持长连接的通信协议,可以进行实时数据交换。在本篇,我们将使用Spring boot,基于原生websocket开发一个web聊天室,并一步步介绍如何在spring boot中开发websocket程序。 一个WebSocket程序包括客户端和服务端。WebSocket客户端除了支持Html5的浏览器外,还包括各大语言提供的WebSocket实现,比如Java中Spring框架的实现,从而在没有浏览器时也能进行websocket通信。HTML5中WebSocket API请看 这里。服务端中,Java定义Java WebSocket API标准 JSR-356[JSR-356],Java主流容器都已经支持websocket,主要包括Tomcat 7.0.47 ,Jetty 9.1 +,GlassFish 4.1 +,WebLogic 12.1.3+和Undertow 1.0(以及WildFly 8.0+)等。 1. 整体功能 现在我们来写一个简单的web的聊天室程序,并一步步学习Spring中是如何封装WebSocket的,这里工程还是使用Spring Boot。 整体功能界面如下: 功能很简单:用户填写上自己的用户名,然后点击链接按钮进入聊天室,然后就可以发送消息给其他人,聊天室中的用户可以看到他人的连入信息和发送的消息。 我们看看在spring boot中如何编写以上程序。 2. 服务端 1、新建一个springboot-websocket的maven工程,引入如下依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> 引入该启动器后,Spring boot会自动引入一下几个启动器或jar包: spring-boot-starter:spring boot启动器 spring-boot-starter-web:spring boot web工程启动器 spring-messaging:包含对消息传递应用程序的基础支持,下一篇用到的STOMP协议的支持也在这个模块。它是Spring Integration项目中的关键抽象,包含Message,MessageChannel,MessageHandler和其他可以作为此类消息传递体系结构基础支撑 spring-websocket: Spring对websocket的支持,它与Java WebSocket API标准( JSR-356[JSR-356])兼容,并且还提供了很多扩展 由此可见,对websocket的支持在spring-websocket和spring-messaing两个模块。 2、编写启动类: @SpringBootApplication public class SpringWebsocketApplication { public static void main(String[] args) { SpringApplication.run(SpringWebsocketApplication.class, args); } } 上边的两步没什么说的,常规的spring boot工程开发。 3、编写websocket握手拦截器: ...

2019-11-01 · 3 min · 577 words · Hank

使用Springboot开发websocket程序(一)——什么是websocket

在互联网飞速发展的当代,浏览器和服务器之间的实时通信已经越来越重要,传统的HTTP协议难以解决实时通信的需求。因此,由HTML5定义的websocket协议应运而生。这里我将用三篇文章来介绍websocket,并使用Springboot开发一个web聊天室的程序,其中会使用原生websocket协议开发,也会单独来介绍使用STOMP协议来开发。 在本篇,我们先看看什么是websocket。 1. 诞生背景 由于HTTP是无状态的协议,采用请求响应模型,服务端是不能主动推送信息给客户端的,只能由客户端发起请求,然后再由服务端进行响应。如果服务端数据有变化,必须通过客户端来获取,服务端是不能主动推送的。要解决实时交换数据的需求,一般的做法是通过轮询(还有http long pull和streaming两种方案,参见这篇 博文)来获得服务端最新信息,即:客户端(浏览器)每隔一段时间(比如1秒)向服务端发起请求,以获取最新的数据。 我们知道,HTTP是一种无状态协议,客户端请求然后服务端响应,则会断开TCP连接。因此,使用轮询的弊端是,客户端不断与服务端建立连接、断开连接,效率非常低下,且非常消耗服务器资源,并且也不是真正意义上的实时。 因此,我们需要一种技术,能够让服务端主动推送数据给客户端,而且消耗资源很少,效率更高。 2. 什么是websocket WebSocket是一种在 单个TCP连接上进行全双工通信的协议,2008诞生,在2011年被IETF定为标准 RFC6455,并由 RFC7936补充规范。WebSocket API也被W3C定为标准,目前各大主流浏览器都已经支持WebSocket。 WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据,这些数据通常包括两种:文本和二进制。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 3. 客户端与服务器的握手 WebSocket基于Http协议来完成握手(Handshake)阶段,在该阶段,客户端需要将服务端请求协议升级,由原来的HTTP协议升级为WebSocket协议,如果服务端能够支持WebSocket,则会告诉客户端升级成功。之后,双方就建立了一条快速通道,可以互相进行数据交互,后续通信就采用WebSocket协议来完成。 Figure 1. websocket与http的区别(图片来源于网络 我们来简单看一下websocket与服务端的握手过程。WebSocket请求头跟HTTP请求头类似,只是添加了一些特殊的头标签,一个握手请求的头信息如下: GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com 可以看到,websocket在握手阶段,采用的仍然是HTTP协议,但是请求添加了一些websocket特有的信息: Upgrade: websocket:就是在请求服务端升级通信协议为WebSocket Sec-WebSocket-Key:浏览器随机生成的一个base64的加密字符串,服务器用它来进行校验请求是来自websocket客户端 Sec-WebSocket-Protocol: 告诉服务器不直接使用websocket,而是使用websocket协议的子协议,例如后文将要介绍的STOMP就是websocket协议的子协议来通信 Sec-WebSocket-Version: 使用websocket协议的版本号 请求发送到服务器,如果服务器也支持websocket则升级成功,并响应如下头信息: HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat 这里,websocket握手完成服务器则会响应101状态码,至此,HTTP协议服务就已经结束,后续通信就采用websocket了。 4. websocket的应用场景 浏览器需要与服务端进行实时数据交换时,都可以使用websocket协议。最常见的如视频直播网站,都有聊天室和弹幕功能,巨大的数据量,如果使用HTTP,交过可想而知。另外,网页游戏开发、也包括一些电商类网站的秒杀、团购等等场景,都会用到websocket。 5. websocket备选方案 目前,各大浏览器都已经支持websocket,如果仍要兼容老版本的浏览器,或者某些代理服务对websocket协议有一些限制,又或者由于连接保持打开时间过长而被浏览器强制断开,导致出现问题,该怎么办呢? ...

2019-11-01 · 1 min · 98 words · Hank