SpringWebFlux-5(WebSocket 学习笔记2021.11.10)

SpringWebFlux -5(5.3.12版学习笔记2021.11.10)

3.0 WebSockets (自行看官网中文文档)

3.1.2. 何时使用 WebSockets

WebSockets 可以使网页具有动态性和交互性。但是,在许多情况下,结合使用 Ajax 和 HTTP 流或长时间轮询可以提供一种简单有效的解决方案。

例如,新闻,邮件和社交订阅源需要动态更新,但是每隔几分钟这样做是完全可以的。另一方面,协作,游戏和金融应用程序需要更接近实时。

仅延迟并不是决定因素。如果消息量相对较少(例如,监视网络故障),则 HTTP 流或轮询可以提供有效的解决方案。低延迟,高频率和高音量的结合才是使用 WebSocket 的最佳案例。

还请记住,在 Internet 上,控件之外的限制性代理可能会阻止 WebSocket 交互,这可能是因为未将它们配置为传递UpgradeHeaders,或者是因为它们关闭了长期处于空闲状态的连接。这意味着与面向公众的应用程序相比,将 WebSocket 用于防火墙内部的应用程序是一个更直接的决定。

3.2. WebSocket API

与 Servlet 堆栈中的相同

Spring 框架提供了一个 WebSocket API,可用于编写处理 WebSocket 消息的 Client 端和服务器端应用程序。

3.2.1. Server

与 Servlet 堆栈中的相同

要创建 WebSocket 服务器,您可以先创建一个WebSocketHandler。以下示例显示了如何执行此操作:

import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;

public class MyWebSocketHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        // ...
    }
}

然后,您可以将其 Map 到 URL 并添加WebSocketHandlerAdapter,如以下示例所示:

@Configuration
static class WebConfig {

    @Bean
    public HandlerMapping handlerMapping() {
        Map<String, WebSocketHandler> map = new HashMap<>();
        map.put("/path", new MyWebSocketHandler());

        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setUrlMap(map);
        mapping.setOrder(-1); // before annotated controllers
        return mapping;
    }

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter();
    }
}
3.2.2. WebSocketHandler

WebSocketHandlerhandle方法采用WebSocketSession并返回Mono<Void>来指示会话的应用程序处理何时完成。通过两个流处理会话,一个流用于入站消息,一个流用于出站消息。下表描述了两种处理流的方法:

WebSocketSession方法Description
Flux<WebSocketMessage> receive()提供对入站消息流的访问,并在关闭连接后完成。
Mono<Void> send(Publisher<WebSocketMessage>)获取传出消息的源,编写消息,然后返回Mono<Void>,该源完成并写入后即完成。

WebSocketHandler必须将入站和出站流组成一个统一的流,并返回Mono<Void>以反映该流的完成。根据应用程序要求,统一流程在以下情况下完成:

  • 入站或出站消息流完成。
  • 入站流完成(即,连接已关闭),而出站流是无限的。
  • 在选定的位置,通过WebSocketSessionclose方法。

将入站和出站消息流组合在一起时,无需检查连接是否打开,因为“响应流”signal 会终止活动。入站流接收完成或错误 signal,而出站流接收取消 signal。

处理程序最基本的实现是处理入站流的实现。以下示例显示了这样的实现:

class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        return session.receive()            (1 =访问入站消息流)
                .doOnNext(message -> {
                    // ...                  (2 =对每条消息进行处理)
                })
                .concatMap(message -> {
                    // ...                  (3 =执行使用消息内容的嵌套异步操作)
                })
                .then();                    (4 =返回一个Mono<Void>,它在接收完成时会完成)
    }
}

对于嵌套的异步操作,您可能需要在使用池化数据缓冲区(例如 Netty)的基础服务器上调用message.retain()。否则,在您有机会读取数据之前,可能会释放数据缓冲区。有关更多背景信息,请参见数据缓冲区和编解码器

以下实现将入站和出站流组合在一起:

class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {

        Flux<WebSocketMessage> output = session.receive()               (1 = 处理入站消息流)
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .map(value -> session.textMessage("Echo " + value));    (2 = 创建出站消息,从而产生组合流)

        return session.send(output);                                    (3 = 返回Mono<Void>,但我们 continue 接收时未完成)
    }
}

入站和出站流可以是独立的,并且只能为了完成而加入,如以下示例所示:

class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {

        Mono<Void> input = session.receive()                                (1 = 处理入站消息流)
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .then();

        Flux<String> source = ... ;
        Mono<Void> output = session.send(source.map(session::textMessage)); (2 =发送传出邮件)

        return Mono.zip(input, output).then();                              (3 = 加入流并返回一个Mono<Void>,当任何一个流结束时,该Mono<Void>完成)
    }
}
3.2.3. DataBuffer

DataBuffer是 WebFlux 中字节缓冲区的表示形式。参考的 Spring Core 部分在数据缓冲区和编解码器的部分中有更多内容。要理解的关键点是,在诸如 Netty 之类的某些服务器上,字节缓冲区被池化并引用计数,并且在消耗字节缓冲区时必须将其释放以避免内存泄漏。

在 Netty 上运行时,如果应用程序希望保留 Importing 数据缓冲区以确保它们不被释放,则必须使用DataBufferUtils.retain(dataBuffer),然后在使用缓冲区时使用DataBufferUtils.release(dataBuffer)

3.2.4. Handshake (握手)

与 Servlet 堆栈中的相同

WebSocketHandlerAdapter代表WebSocketService。默认情况下,它是HandshakeWebSocketService的实例,该实例对 WebSocket 请求执行基本检查,然后将RequestUpgradeStrategy用于所使用的服务器。当前,内置了对 Reactor Netty,Tomcat,Jetty 和 Undertow 的支持。

HandshakeWebSocketService公开了sessionAttributePredicate属性,该属性允许设置Predicate<String>以从WebSession提取属性并将其插入WebSocketSession的属性。

3.2.5. 服务器配置

与 Servlet 堆栈中的相同

RequestUpgradeStrategy每个服务器公开配置的具体到下面的WebSocket服务器引擎。使用 WebFlux Java 配置时,您可以自定义此类属性,如WebFlux 配置的相应部分所示 ,否则,如果不使用 WebFlux 配置, 使用以下示例

在 Tomcat 上运行时设置 WebSocket 选项:

@Configuration
static class WebConfig {

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter(webSocketService());
    }

    @Bean
    public WebSocketService webSocketService() {
        TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
        strategy.setMaxSessionIdleTimeout(0L);
        return new HandshakeWebSocketService(strategy);
    }
}

检查服务器的升级策略,以查看可用的选项。当前,只有 Tomcat 和 Jetty 公开了此类选项。

3.2.6. CORS

与 Servlet 堆栈中的相同

配置 CORS 并限制对 WebSocket 端点的访问的最简单方法是让WebSocketHandler实现CorsConfigurationSource并返回CorsConfiguraiton并带有允许的来源,Headers 和其他详细信息。如果您不能这样做,则还可以在SimpleUrlHandler上设置corsConfigurations属性,以通过 URL 模式指定 CORS 设置。如果同时指定了两者,则使用CorsConfiguration上的combine方法将它们组合在一起。

3.2.7. Client (客户端)

Spring WebFlux 为 Reactor Netty,Tomcat,Jetty,Undertow 和标准 Java(即 JSR-356)的实现提供了WebSocketClient抽象。

TomcatClient 端实际上是标准 JavaClient 端的扩展,在WebSocketSession处理中具有一些额外功能,以利用特定于 Tomcat 的 API 暂停接收消息以产生反压。

要启动 WebSocket 会话,您可以创建 Client 端的实例并使用其execute方法:

WebSocketClient client = new ReactorNettyWebSocketClient();

URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->
        session.receive()
                .doOnNext(System.out::println)
                .then());

某些 Client 端(例如 Jetty)实现Lifecycle,并且需要先停止然后启动,然后才能使用它们。所有 Client 端都具有与基础 WebSocketClient 端的配置有关的构造器选项。

4. Testing (测试)

在 Spring MVC 中相同

spring-test模块提供ServerHttpRequestServerHttpResponseServerWebExchange的模拟实现。有关模拟对象的讨论,请参见Spring WebReactive

WebTestClient构建在这些模拟请求和响应对象的基础上,以提供对不使用 HTTP 服务器的 WebFlux 应用程序测试的支持。您也可以使用WebTestClient进行端到端集成测试。

5.RSocket (详情看官网)

本节介绍 Spring Framework 对 RSocket 协议的支持。

5.1. 概述

RSocket 是一种应用协议,用于通过 TCP、WebSocket 和其他字节流传输进行多路复用、双工通信,使用以下交互模型之一:

  • Request-Response — 发送一条消息并收到一条消息。
  • Request-Stream — 发送一条消息并接收返回的消息流。
  • Channel — 双向发送消息流。
  • Fire-and-Forget — 发送单向消息。

一旦建立了初始连接,“客户端”与“服务器”的区别就会消失,因为双方变得对称并且每一方都可以发起上述交互之一。这就是为什么在协议中将参与方称为“请求者”和“响应者”,而将上述交互称为“请求流”或简称为“请求”。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懵懵懂懂程序员

如果节省了你的时间, 请鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值