目录
WebSocket
是一种基于 TCP
的网络协议。在 2009 年诞生,于 2011 年被 IETF 定为标准 RFC 6455
通信标准,并由 RFC7936
补充规范。WebSocket API
也被 W3C
定为标准。
WebSocket
也是一种全双工通信的协议,既允许客户端向服务器主动发送消息,也允许服务器主动向客户端发送消息。在 WebSocket
中,浏览器和服务器只需要完成一次握手,两者之间就可以建立持久性的连接,进行双向数据传输。
HTTP
协议;ws
,如果采用加密则是 wss
;TCP
协议之上,服务器端的实现比较容易;WebSocket
可以发送文本,也可以发送二进制数据;HTTP
协议有着良好的兼容性。默认端口也是 80
和 443
,并且握手阶段采用 HTTP
协议,因此握手时不容易屏蔽,能通过各种 HTTP
代理服务器;谈起为什么需要 WebSocket
前,那得先了解在没有 WebSocket
那段时间说起,那时候基于 Web
的消息基本上是靠 Http
协议进行通信,而经常有”聊天室”、”消息推送”、”股票信息实时动态”等这样需求,而实现这样的需求常用的有以下几种解决方案:
短轮询是指客户端每隔一段时间就询问一次服务器是否有新的消息,如果有就接收消息。这样方式会增加很多次无意义的发送请求信息,每次都会耗费流量及处理器资源。
优点:短连接,服务器处理简单,支持跨域、浏览器兼容性较好。
缺点:有一定延迟、服务器压力较大,浪费带宽流量、大部分是无效请求。
长轮询是段轮询的改进,客户端执行 HTTP
请求发送消息到服务器后,等待服务器回应,如果没有新的消息就一直等待,知道服务器有新消息传回或者超时。
这也是个反复的过程,这种做法只是减小了网络带宽和处理器的消耗,但是带来的问题是导致消息实时性低,延迟严重。而且也是基于循环,最根本的带宽及处理器资源占用并没有得到有效的解决。
优点:减少轮询次数,低延迟,浏览器兼容性较好。
缺点:服务器需要保持大量连接。
“目前除了
IE/Edge
,其他浏览器都支持。
服务器发送事件是一种服务器向浏览器客户端发起数据传输的技术。一旦创建了初始连接,事件流将保持打开状态,直到客户端关闭。该技术通过传统的 HTTP
发送,并具有 WebSockets
缺乏的各种功能,例如”自动重新连接”、”事件ID” 及 “发送任意事件”的能力。
“服务器发送事件是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载。
优点:适用于更新频繁、低延迟并且数据都是从服务端发到客户端。
缺点:浏览器兼容难度高。
显然,上面这几种方式都有各自的优缺点,虽然靠轮询方式能够实现这些一些功能,但是其对性能的开销和低效率是非常致命的,尤其是在移动端流行的现在。
现在客户端与服务端双向通信的需求越来越多,且现在的浏览器大部分都支持 WebSocket
。所以对实时性和双向通信及其效率有要求的话,比较推荐使用 WebSocket
。
客户端先用带有 Upgrade:Websocket
请求头的 HTTP
请求,向服务器端发起连接请求,实现握手(HandShake
)。
客户端 HTTP
请求的 Header
头信息如下:
Connection
: Upgrade 表示要升级协议。Upgrade
: Websocket 要升级协议到 websocket 协议。Sec-WebSocket-Extensions
: 表示客户端所希望执行的扩展(如消息压缩插件)。Sec-WebSocket-Key
: 主要用于WebSocket协议的校验,对应服务端响应头的 Sec-WebSocket-Accept
。Sec-WebSocket-Version
: 表示 websocket
的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader
,里面包含服务端支持的版本号。握手成功后,由 HTTP
协议升级成 Websocket
协议,进行长连接通信,两端相互传递信息。
服务端响应的 HTTP Header 头信息如下:
Connection
: Upgrade 表示要升级协议。Upgrade
: Websocket 要升级协议到 websocket 协议。Sec-Websocket-Accept
: 对应 Sec-WebSocket-Key
生成的值,主要是返回给客户端,让客户端对此值进行校验,证明服务端支持 WebSocket
。WebSocket
确实指定了一种消息传递体系结构,但并不强制使用任何特定的消息传递协议。而且它是 TCP 上的一个非常薄的层,它将字节流转换为消息流(文本或二进制)仅此而已。由应用程序来解释消息的含义。
与 HTTP
(它是应用程序级协议)不同,在 WebSocket
协议中,传入消息中根本没有足够的信息供框架或容器知道如何路由或处理它。因此,对于非常琐碎的应用程序而言 WebSocket
协议的级别可以说太低了。
可以做到的是引导在其上面再创建一层框架。这就相当于当今大多数 Web
应用程序使用的是 Web
框架,而不直接仅使用 Servlet API
进行编码一样。
WebSocket RFC
定义了子协议的使用。在握手过程中,客户机和服务器可以使用头 Sec-WebSocket
协议商定子协议,即使不需要使用子协议,而是用更高的应用程序级协议,但应用程序仍需要选择客户端和服务器都可以理解的消息格式。且该格式可以是自定义的、特定于框架的或标准的消息传递协议。
Spring
框架支持使用 STOMP
,这是一个简单的消息传递协议,最初创建用于脚本语言,框架灵感来自 HTTP
。STOMP
被广泛支持,非常适合在 WebSocket
和 web
上使用。
(1). STOMP 协议概述
“STOMP(Simple Text-Orientated Messaging Protocol)是一种简单的面向文本的消息传递协议。
它提供了一个可互操作的连接格式,允许 STOMP
客户端与任意 STOMP
消息代理(Broker
)进行交互。STOMP
协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。
(2). 简单介绍可以分为以下几点:
STOMP
是基于帧的协议,其帧以 HTTP
为模型。
STOMP
框架由命令,一组可选的标头和可选的主体组成。
STOMP
基于文本,但也允许传输二进制消息。
STOMP
的默认编码为 UTF-8
,但它支持消息正文的替代编码的规范。
(3). STOMP 客户端是一种用户代理
作为生产者,通过 SEND 帧将消息发送到目标服务器上。
作为消费者,对目标地址发送 SUBSCRIBE 帧,并作为 MESSAGE 帧从服务器接收消息。
(4). STOMP 帧
STOMP
是基于帧的协议,其帧以 HTTP
为模型。STOMP
结构为:
客户端可以使用 SEND
或 SUBSCRIBE
命令发送或订阅消息,还可以使用 “destination
” 头来描述消息的内容和接收者。
这支持一种简单的发布-订阅机制,可用于通过代理将消息发送到其他连接的客户端,或将消息发送到服务器以请求执行某些工作。
(5). Stomp 常用帧
STOMP
的客户端和服务器之间的通信是通过”帧“(Frame
)实现的,每个帧由多”行“(Line
)组成,其包含的帧如下:
(6). Stomp 与 WebSocket 的关系
直接使用 WebSocket
就很类似于使用 TCP
套接字来编写 Web
应用,因为没有高层级的应用协议(wire protocol),因而就需要我们定义应用之间所发送消息的语义,还需要确保连接的两端都能遵循这些语义。
同 HTTP
在 TCP
套接字上添加请求-响应模型层一样,STOMP
在 WebSocket
之上提供了一个基于帧的线路格式层,用来定义消息语义。
(7). 使用 STOMP 作为 WebSocket 子协议的好处
RabbitMQ
,ActiveMQ
等)进行广播的选项STOMP
(相对于普通 WebSocket
)使 Spring Framework
能够为应用程序级使用提供编程模型,就像 Spring MVC
提供基于 HTTP
的编程模型一样。使用 Spring
的 STOMP
支持时,Spring WebSocket
应用程序充当客户端的 STOMP
代理。
消息被路由到 @Controller
消息处理方法或简单的内存中代理,该代理跟踪订阅并向订阅的用户广播消息。
还可以将 Spring
配置为与专用的 STOMP
代理(例如 RabbitMQ
,ActiveMQ
等)一起使用,以实际广播消息。在那种情况下,Spring
维护与代理的 TCP
连接,将消息中继到该代理,并将消息从该代理向下传递到已连接的 WebSocket
客户端。
因此 Spring Web
应用程序可以依赖基于统一 HTTP
的安全性,通用验证以及熟悉的编程模型消息处理工作。
Spring 官方提供的处理流图:
上面中的一些概念关键词:
Message
: 消息,里面带有 header
和 payload
。MessageHandler
: 处理 client
消息的实体。MessageChannel
: 解耦消息发送者与消息接收者的实体clientInboundChannel
:用于从 WebSocket 客户端接收消息。clientOutboundChannel
:用于将服务器消息发送给 WebSocket 客户端。brokerChannel
:用于从服务器端、应用程序中向消息代理发送消息Broker
: 存放消息的中间件,client
可以订阅 broker
中的消息。上面的设置包括3个消息通道:
clientInboundChannel
: 用于来自WebSocket客户端的消息。clientOutboundChannel
: 用于向WebSocket客户端发送消息。brokerChannel
: 从应用程序内部发送给代理的消息。WebSocket
常分为广播与队列模式,广播模式是向订阅广播的用户发送信息,只要订阅相关广播就能收到对应信息。
队列模式常用于点对点模式,为单个用户向另一个用户发送信息,这里先介绍下广播模式的实现示例。
这里使用 Maven 工具管理依赖包,分别引入下面依赖:
lombok
: Lombok 工具依赖,便于生成实体对象的 Get 与 Set 方法。spring-boot-starter-websocket
:SpringBoot 实现 WebSocket 的依赖,里面对 WebSocket 进行了一些列封装,并且也包含了 SpringBoot Web 依赖。创建便于传输消息的实体类,里面字段内容如下:
创建 WebSocket
配置类,配置进行连接注册的端点 /mydlq
和消息代理前缀 /topic
及接收客户端发送消息的前缀 /app
。
创建 Controller
类,该类也类似于正常 Web
项目中 Controller
写法一样,在方法上面添加 @MessageMapping
注解,当客户端发送消息请求的前缀匹配上 WebSocket 配置类中的 /app
前缀后,会进入到 Controller
类中进行匹配,如果匹配成功则执行注解所在的方法内容。
创建用于操作 WebSocket 的 JS 文件 app-websocket.js,内容如下:
创建用于展示 WebSocket 相关功能的 WEB HTML 页面 index.html,内容如下:
输入地址 http://localhost:8080/index.html
访问测试的前端页面,然后执行下面步骤进行测试:
hello world!
,然后点击发送按钮发送消息;执行完上面步骤成后,可以观察到成功接收到订阅地址的消息,如下:
这里使用 Maven 工具管理依赖包,分别引入下面依赖:
lombok
: Lombok 工具依赖,便于生成实体对象的 Get 与 Set 方法。spring-boot-starter-websocket
:SpringBoot 实现 WebSocket 的依赖,里面对 WebSocket 进行了一些列封装,并且也包含了 SpringBoot Web 依赖。spring-boot-starter-security
:Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。创建便于传输消息的实体类,里面字段内容如下:
创建 WebSocket
配置类,配置进行连接注册的端点/mydlq
和消息代理前缀 /queue
及接收客户端发送消息的前缀 /app
。
Spring Security 的配置类,可以在该类中配置权限认证及测试的两个用户相关信息:
跟上面介绍广播模式一样,作用也是根据 WebSocket
配置类中 /app
前缀匹配后进入 Controller
类进行逻辑处理操作。
创建用于操作 WebSocket 的 JS 文件 app-websocket.js,内容如下:
创建用于展示 WebSocket 相关功能的 WEB HTML 页面 index.html,内容如下:
为了方便测试,需要打开两个不同类型浏览器(因为用户登录后会存 Session
,如果一个浏览器不同用户登录会使之前 Session
失效)来进行测试,两个浏览器同时输入地址 http://localhost:8080/index.html
访问测试的前端页面,然后可以看到并没有进入 /index.html
页面,而是跳转到Spring Security
提供的登录的 /login
页面,如下:
两个浏览器中都输入用户名/密码 mydlq1/123456
与 mydlq2/123456
进行登录,然后会回到 /index.html
页面,然后执行下面步骤进行测试:
mydlq1
)设置发送目标用户为”/mydlq2
”,”浏览器2”(用户 mydlq2
)设置发送目标用户为”/mydlq1
”;mydlq1
)设置发送消息为Hi, I’m mydlq1
,”浏览器2”(用户 mydlq2
)设置发送消息为Hi, I’m mydlq2
;发送
按钮发送消息;执行完上面步骤成后,可以在两个不同浏览器中观察到如下内容:
“同示例二
配置 WebSocket 通道拦截器,里面添加两个模拟用户:
mydlq1
,Token
:123456-1mydlq2
,Token
:123456-2创建 WebSocket 配置类,配置进行连接注册的端点 /mydlq
和消息代理前缀 /queue
及接收客户端发送消息的前缀 /app
。
创建用于操作 WebSocket 的 JS 文件 app-websocket.js,内容如下:
创建用于展示 WebSocket 相关功能的 WEB HTML 页面 index.html,内容如下:
为了方便测试,需要打开两个不同类型浏览器(这里模拟通过 Header 传 Token 的方式进行用户验证,具体登录逻辑不实现,而是直接使用事先配置好的两个用户 Token 进行模拟)来进行测试,两个浏览器同时输入地址 http://localhost:8080/index.html
访问测试的前端页面 ``/index.html` 如下:
两个浏览器中都执行下面步骤进行测试:
/abc
,然后点击订阅按钮进行消息订阅;/mydlq2
,浏览器2(用户 mydlq2)设置发送目标用户为/mydlq1
;执行完上面步骤成后,可以在两个不同浏览器中观察到如下内容:
WebSocket 配置类,里面设置允许跨域,内容如下:
创建 WebSocket 用户上线、下线处理器,内容如下:
WebSocket 配置类中实现 configureWebSocketTransport()
方法,将上面 WebSocket 处理器加到其中,如下: