返回介绍

18.1 使用 Spring 的低层级 WebSocket API

发布于 2024-08-17 00:45:49 字数 4770 浏览 0 评论 0 收藏 0

按照其最简单的形式,WebSocket只是两个应用之间通信的通道。位于WebSocket一端的应用发送消息,另外一端处理消息。因为它是全双工的,所以每一端都可以发送和处理消息。如图18.1所示。

图18.1 WebSocket是两个应用之间全双工的通信通道

WebSocket通信可以应用于任何类型的应用中,但是WebSocket最常见的应用场景是实现服务器和基于浏览器的应用之间的通信。浏览器中的JavaScript客户端开启一个到服务器的连接,服务器通过这个连接发送更新给浏览器。相比历史上轮询服务端以查找更新的方案,这种技术更加高效和自然。

为了阐述Spring低层级的WebSocket API,让我们编写一个简单的WebSocket样例,基于JavaScript的客户端与服务器玩一个无休止的“Marco Polo”游戏。服务器端的应用会处理文本消息(“Marco!”),然后在相同的连接上往回发送文本消息(“Polo!”)。为了在Spring使用较低层级的API来处理消息,我们必须编写一个实现WebSocketHandler的类:

可以看到,WebSocketHandler需要我们实现五个方法。相比直接实现WebSocketHandler,更为简单的方法是扩展AbstractWebSocketHandler,这是WebSocketHandler的一个抽象实现。如下的程序清单展现了MarcoHandler,它是AbstractWebSocketHandler的一个子类,会在服务器端处理消息。

程序清单18.1 MarcoHandler处理通过WebSocket传送的文本消息

尽管AbstractWebSocketHandler是一个抽象类,但是它并不要求我们必须重载任何特定的方法。相反,它让我们来决定该重载哪一个方法。除了重载WebSocketHandler中所定义的五个方法以外,我们还可以重载AbstractWebSocketHandler中所定义的三个方法:

handleBinaryMessage()

handlePongMessage()

handleTextMessage()

这三个方法只是handleMessage()方法的具体化,每个方法对应于某一种特定类型的消息。

因为MarcoHandler将会处理文本类型的“Marco!”消息,因此我们应该重载handleTextMessage()方法。当有文本消息抵达的时候,日志会记录消息内容,在两秒钟的模拟延迟之后,在同一个连接上返回另外一条文本消息。

MarcoHandler所没有重载的方法都由AbstractWebSocketHandler以空操作的方式(no-op)进行了实现。这意味着MarcoHandler也能处理二进制和pong消息,只是对这些消息不进行任何操作而已。

另外一种方案,我们可以扩展TextWebSocketHandler,不再扩展Abstract-WebSocketHandler:

TextWebSocketHandler是AbstractWebSocketHandler的子类,它会拒绝处理二进制消息。它重载了handleBinaryMessage()方法,如果收到二进制消息的时候,将会关闭WebSocket连接。与之类似,BinaryWebSocketHandler也是AbstractWeb-SocketHandler的子类,它重载了handleTextMessage()方法,如果接收到文本消息的话,将会关闭连接。

尽管你会关心如何处理文本消息或二进制消息,或者二者兼而有之,但是你可能还会对建立和关闭连接感兴趣。在本例中,我们可以重载afterConnectionEstablished()和afterConnectionClosed():

我们通过afterConnectionEstablished()和afterConnectionClosed()方法记录了连接信息。当新连接建立的时候,会调用afterConnectionEstablished()方法,类似地,当连接关闭时,会调用afterConnectionClosed()方法。在本例中,连接事件仅仅记录了日志,但是如果我们想在连接的生命周期上建立或销毁资源时,这些方法会很有用。

注意,这些方法都是以“after”开头。这意味着,这些事件只能在事件发生后才产生响应,因此并不能改变结果。

现在,已经有了消息处理器类,我们必须要对其进行配置,这样Spring才能将消息转发给它。在Spring的Java配置中,这需要在一个配置类上使用@EnableWebSocket,并实现WebSocketConfigurer接口,如下面的程序清单所示。

程序清单18.2 在Java配置中,启用WebSocket并映射消息处理器

registerWebSocketHandlers()方法是注册消息处理器的关键。通过重载该方法,我们得到了一个WebSocketHandlerRegistry对象,通过该对象可以调用addHandler()来注册信息处理器。在本例中,我们注册了MarcoHandler(以bean的方式进行声明)并将其与“/marco”路径相关联。

另外,如果你更喜欢使用XML来配置Spring的话,那么可以使用websocket命名空间:

程序清单18.3 借助websocket命名空间以XML的方式配置WebSocket

不管使用Java还是使用XML,这就是所需的配置。

现在,我们可以把注意力转向客户端,它会发送“Marco!”文本消息到服务器,并监听来自服务器的文本消息。如下程序清单所展示的JavaScript代码开启了一个原始的WebSocket并使用它来发送消息给服务器。

程序清单18.4 连接到“marco” WebSocket的JavaScript客户端

在程序清单18.4的代码中,所做的第一件事情就是创建WebSocket实例。对于支持WebSocket的浏览器来说,这个类型是原生的。通过创建WebSocket实例,实际上打开了到给定URL的WebSocket。在本例中,URL使用了“ws://”前缀,表明这是一个基本的WebSocket连接。如果是安全WebSocket的话,协议的前缀将会是“wss://”。

WebSocket创建完毕之后,接下来的代码建立了WebSocket的事件处理功能。注意,WebSocket的onopen、onmessage和onclose事件对应于MarcoHandler的after-ConnectionEstablished()、handleTextMessage()和afterConnectionClosed()方法。在onopen事件中,设置了一个函数,它会调用sayMarco()方法,在该WebSocket上发送“Marco!”消息。通过发送“Marco!”,这个无休止的Marco Polo游戏就开始了,因为服务器端的MarcoHandler作为响应会将“Polo!”发送回来,当客户端收到来自服务器的消息后,onmessage事件会发送另外一个“Marco!”给服务器。

这个过程会一直持续下去,直到连接关闭。在程序清单18.4中所没有展示的是如果调用sock.close()的话,将会结束这个疯狂的游戏。在服务端也可以关闭连接,或者浏览器转向其他的页面,都会关闭连接。如果发生以上任意的场景,只要连接关闭,都会触发onclose事件。在这里,出现这种情况将会在控制台日志上记录一条信息。

到此为止,我们已经编写完使用Spring低层级WebSocket API的所有代码,包括接收和发送消息的处理器类,以及在浏览器端完成相同功能的JavaScript客户端。如果我们构建这些代码并将其部署到Servlet容器中,那它有可能能够正常运行。

从我选择“可能”这个词,你是不是能够感觉到这里有一点悲观的情绪?这是因为我不能保证它可以正常运行。实际上,它很有可能运行不起来。即便把所有的事情都做对了,诡异的事情依然会困扰我们。

让我们看一下都有什么事情会阻止WebSocket正常运行,并采取一些措施提高成功的几率。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文