WebSocket
概述
WebSocket协议提供了通过一个套接字实现全双工通信的功能。除了其他的功能之外,它能够实现Web浏览器和服务器之间的异步通信。全双工意味着服务器可以发送消息给浏览器,浏览器也可以发送消息给服务器。
使用Spring的低层级WebSocketAPI
按照其最简单的形式,WebSocket只是两个应用之间通信的通道。位于WebSocket一端的应用发送消息,另一端接收消息。因为它是全双工的,所以每一端都可以发送和处理消息。
WebSocket通信可以应用于任何类型的应用中,但是WebSocket最常见的应用场景是实现服务器和基于浏览器的应用之间的通信。
编写简单的WebSocket样例(基于JavaScript的客户端与服务器的一个无休止的“Marco Polo”游戏)
为了在Spring使用较底层级的API来处理消息,我们必须编写一个实现WebSocketHandler的类。
WebSocketHandler.java
1 | java复制代码public interface WebSocketHandler { |
不过更为简单的方法是扩展AbstractWebSocketHandler,这是WebSocketHandler的一个抽象实现。
MarcoHandler.java
1 | java复制代码public class MarcoHandler extends AbstractWebSocketHandler { |
尽管AbstractWebSocketHandler是一个抽象类,但是它并不要求我们必须重载任何特定的方法。相反,它让我们来决定该重载哪一个方法。除了重载WebSocketHandler中定义的五个方法以外,我们还可以重载AbstractWebSocketHandler中所定义的三个方法:
- handleBinaryMessage()
- handlePongMessage()
- handleTextMessage()
这三个方法只是handleMessage()方法的具体化,每个方法对应于某一种特定类型的消息。
所以没有重载的方法都由AbstractWebSocketHandler以空操作的方式进行。这意味着MarcoHandler也能处理二进制和pong消息,只是对这些消息不进行任何操作而已。
另外一种方案我们可以扩展TextWebSocketHandler,TextWebSocketHandler是AbstractWebSocketHandler的子类,它会拒绝处理二进制消息。它重载了handleBinaryMessage()方法,如果收到二进制消息,将会关闭WebSocket连接。与之类似,BinaryWebSocketHandler也是AbstractWebSocketHandler的子类,它重载了handleTextMessage()方法,如果收到文本消息的话,将会关闭连接。
1 | java复制代码public class MarcoHandler extends TextWebSocketHandler { |
WebSocketConfig.java
1 | java复制代码@Configuration |
WebAppInitializer.java
1 | java复制代码@Override |
JavaScript客户端代码
1 | xml复制代码<script> |
在本例中,URL使用了ws://前缀,表明这是一个基本的WebSocket连接,如果是安全WebSocket的话,协议的前缀将会是wss://。
注意: jar包一定要导正确,我是用的Spring5.0、jackson2.9.3。一些老版本的jar包老是报各种NoSuchMethodException,又或者Spring与jackson版本不兼容
WebSocket简单示例
个人感觉上面的那种太复杂了,如果只是简单的通信的话,可以像下面这样写:
1 | xml复制代码<script> |
1 | java复制代码import java.io.IOException; |
运行,浏览器与服务端的输出如图:
在这里插入图片描述
在这里插入图片描述
SockJS
==========================
概述
WebSocket是一个相对比较新的规范,在Web浏览器和应用服务器上没有得到一致的支持。所以我们需要一种WebSocket的备选方案。
而这恰恰是SockJS所擅长的。SockJS是WebSocket技术的一种模拟,在表面上,它尽可能对应WebSocket API,但是在底层非常智能。如果WebSocket技术不可用的话,就会选择另外的通信方式。
使用SockJS
WebSocketConfig.java
1 | java复制代码 @Override |
只需加上withSockJS()方法就能声明我们想要使用SockJS功能,如果WebSocket不可用的话,SockJS的备用方案就会发挥作用。
JavaScript客户端代码
要在客户端使用SockJS,需要确保加载了SockJS客户端库。
1 | xml复制代码<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script> |
除了加载SockJS客户端库外,要使用SockJS只需要修改两行代码即可:
1 | ruby复制代码 var url = 'marco'; |
运行效果一样,但是客户端–服务器之间通信的方式却有了很大的变化。
使用STOMP消息
概述
STOMP在WebSocket之上提供了一个基于帧的线路格式层,用来定义消息的语义。STOMP帧由命令、一个或多个头信息以及负载所组成。例如如下就是发送数据的一个STOMP帧:
1 | javascript复制代码>>> SEND |
在这个简单的样例中,STOMP命令是SEND,表明会发送一些内容。紧接着是两个头信息:一个用来表示消息要发送到哪里的目的地,另外一个则包含了负载的大小。然后,紧接着是一个空行,STOMP帧的最后是负载内容。
STOMP帧中最有意思的是destination头信息了。它表明STOMP是一个消息协议。消息会发布到某个目的地,这个目的地实际上可能真的有消息代理作为支撑。另一方面,消息处理器也可以监听这些目的地,接收所发送过来的消息。
启用STOMP消息功能
WebSocketStompConfig.java
1 | java复制代码@Configuration |
1 | java复制代码 @Override |
WebSocketStompConfig 重载了registerStompEndpoints()方法,将/marcopolo注册为STOMP端点。这个路径与之前接收和发送消息的目的地路径有所不同。这是一个端点,客户端在订阅或发布消息到目的地前,要连接该端点。
WebSocketStompConfig还通过重载configureMessageBroker()方法配置了一个简单的消息代理。这个方法是可选的,如果不重载它的话,将会自动配置一个简单的内存消息代理,用它来处理以“/topic”为前缀的消息。
处理来自客户端的STOMP消息
testConroller.java
1 | java复制代码@Controller |
@MessageMapping注解,表明handleShout()方法能够处理指定目的地上到达的消息。本例中目的地也就是“/app/marco”。(“/app”前缀是隐含 的,因为我们将其配置为应用的目的地前缀)
@SubscribeMapping注解,与@MessageMapping注解相似,当收到了STOMP订阅消息的时候,带有@SubscribeMapping注解的方法将会被触发。
Shout.java
1 | java复制代码public class Shout { |
客户端JavaScript代码
1 | xml复制代码<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script> |
Received message:Marco!
发送消息到客户端
如果你想要在接收消息的时候,同时在响应中发送一条消息,那么需要做的仅仅是将内容返回就可以了。
1 | java复制代码@MessageMapping("/marco") |
当@MessageMapping注解标示的方法有返回值的时候,返回的对象将会进行转换(通过消息转换器)并放到STOMP帧的负载中,然后发给消息代理。
默认情况下,帧所发往的目的地会与触发处理器方法的目的地相同,只不过会加上“/topic”前缀。
1 | javascript复制代码stomp.subscribe('/topic/marco', function(message){ 订阅后将会接收到消息。 |
不过我们可以通过为方法添加@SendTo注解,重载目的地:
1 | java复制代码@MessageMapping("/marco") |
1 | javascript复制代码stomp.subscribe('/queue/marco', function(message){ |
在应用的任意地方发送消息
Spring的SimpMessagingTemplate能够在应用的任何地方发送消息,甚至不必以首先接收一条消息作为前提。
使用SimpMessagingTemplate的最简单方式是将它(或者其接口SimpMessageSendingOperations)自动装配到所需的对象中。
1 | java复制代码 @Autowired |
访问/test后:
为目标用户发送消息
使用@SendToUser注解,表明它的返回值要以消息的形式发送给某个认证用户的客户端。
1 | java复制代码 @MessageMapping("/message") |
1 | javascript复制代码stomp.subscribe('/users/topic/sendtouser', function(message){//给指定用户发送一对一的消息前缀是/users/。 |
这个目的地使用了/users作为前缀,以/users作为前缀的目的地将会以特殊的方式进行处理。以/users为前缀的消息将会通过UserDestinationMessageHandler进行处理。
UserDestinationMessageHandler的主要任务是将用户消息重新路由到某个用户独有的目的地上。在处理订阅的时候,它会将目标地址中的/users前缀去掉,并基于用户的会话添加一个后缀。
为指定用户发送消息
SimpMessagingTemplate还提供了convertAndSendToUser()方法。convertAndSendToUser()方法能够让我们给特定用户发送消息。
1 | arduino复制代码simpMessageSendingOperations.convertAndSendToUser("1", "/message", "测试convertAndSendToUser"); |
1 | javascript复制代码stomp.subscribe('/users/1/message', function(message){ |
客户端接收一对一消息的主题是”/users/“+usersId+”/message”,这里的用户Id可以是一个普通字符串,只要每个客户端都使用自己的Id并且服务器端知道每个用户的Id就行了。
以上只是学习所做的笔记,如有错误请指正。谢谢啦!!!
本文转载自: 掘金