如何使用Spring WebFlux和WebSocket确保反应流完成

如何使用Spring WebFlux和WebSocket确保反应流完成,websocket,kotlin,reactive-programming,spring-webflux,project-reactor,Websocket,Kotlin,Reactive Programming,Spring Webflux,Project Reactor,我已经在Kotlin为SpringWebFlux编写了一个测试客户端和服务器。客户端向服务器发送一个数字(例如4)并返回那么多数字(例如0、1、2和3)。以下是服务器实现: class NumbersWebSocketHandler : WebSocketHandler { override fun handle(session: WebSocketSession): Mono<Void> { var index = 0 var count =

我已经在Kotlin为SpringWebFlux编写了一个测试客户端和服务器。客户端向服务器发送一个数字(例如4)并返回那么多数字(例如0、1、2和3)。以下是服务器实现:

class NumbersWebSocketHandler : WebSocketHandler {
    override fun handle(session: WebSocketSession): Mono<Void> {
        var index = 0
        var count = 1
        val publisher = Flux.generate<Int> { sink ->
            if (index < count) {
                sink.next(index)
                index++
            } else {
                sink.complete()
            }
        }.map(Int::toString)
            .map(session::textMessage)
            .delayElements(Duration.ofMillis(500))

        return session.receive()
            .map(WebSocketMessage::getPayloadAsText)
            .doOnNext {
                println("About to send $it numbers")
                count = it.toInt()
            }
            .then()
            .and(session.send(publisher))
            .then()
    }
}
而且:

.delayElements(Duration.ofMillis(500))
.doFinally { session.close() }
但两者都不会对客户的行为产生任何影响。在调用“send”后尝试显式关闭会话也不会:

.and(session.send(publisher))
.then()
.and { session.close() }
.then()
从中可以看出,它希望上游发布服务器发送一个
onComplete
事件来发出事件本身。我不确定,但我认为只有当连接正常终止时才会发出。这就是为什么永远不会执行
doOnNext

我认为您应该使用
subscribe()
来代替
reduce
,并定义您自己的
Subscriber
实例,您可以在其中保存状态

编辑:您不能在此处使用
subscribe
方法。但如果服务器上的连接已关闭,则应该是这样:

.map(Int::toString)
        .map(session::textMessage)
        .delayElements(Duration.ofMillis(500))
        .then(session::close)
从中可以看出,它希望上游发布服务器发送一个
onComplete
事件来发出事件本身。我不确定,但我认为只有当连接正常终止时才会发出。这就是为什么永远不会执行
doOnNext

我认为您应该使用
subscribe()
来代替
reduce
,并定义您自己的
Subscriber
实例,您可以在其中保存状态

编辑:您不能在此处使用
subscribe
方法。但如果服务器上的连接已关闭,则应该是这样:

.map(Int::toString)
        .map(session::textMessage)
        .delayElements(Duration.ofMillis(500))
        .then(session::close)
WebSocketHandler
返回的
Mono
指示处理何时完成,进而指示WebSocket连接应保持打开的时间。问题是,在两侧,返回的
Mono
永远不会完成

客户端使用
reduce
等待来自服务器端的输入结束,但服务器使用
receive()
+
doOnNext
+
then()
永远等待接收消息。所以客户端和服务器都在等待对方。在客户端添加
take
有助于打破僵局:

client.execute(uri, session -> session
        .send(input.map(session::textMessage))
        .then(session.receive()
                .map(WebSocketMessage::getPayloadAsText)
                .map(Integer::valueOf)
                .take(3)
                .reduce((a,b) -> {
                    logger.debug("Reduce called with " + a + " and " + b);
                    return a + b;
                })
                .doOnNext(logger::debug)
        )
        .then()
).block();
第二个问题是服务器的组成不正确。发送与接收通过
并行触发。相反,发送流应取决于首先接收计数:

session.receive()
        .map(WebSocketMessage::getPayloadAsText)
        .flatMap(s -> {
            logger.debug("About to send " + s + " numbers");
            count.set(Integer.parseInt(s));
            return session.send(publisher);
        })
        .then()
WebSocketHandler
返回的
Mono
指示处理何时完成,进而指示WebSocket连接应保持打开的时间。问题是,在两侧,返回的
Mono
永远不会完成

客户端使用
reduce
等待来自服务器端的输入结束,但服务器使用
receive()
+
doOnNext
+
then()
永远等待接收消息。所以客户端和服务器都在等待对方。在客户端添加
take
有助于打破僵局:

client.execute(uri, session -> session
        .send(input.map(session::textMessage))
        .then(session.receive()
                .map(WebSocketMessage::getPayloadAsText)
                .map(Integer::valueOf)
                .take(3)
                .reduce((a,b) -> {
                    logger.debug("Reduce called with " + a + " and " + b);
                    return a + b;
                })
                .doOnNext(logger::debug)
        )
        .then()
).block();
第二个问题是服务器的组成不正确。发送与接收通过
并行触发。相反,发送流应取决于首先接收计数:

session.receive()
        .map(WebSocketMessage::getPayloadAsText)
        .flatMap(s -> {
            logger.debug("About to send " + s + " numbers");
            count.set(Integer.parseInt(s));
            return session.send(publisher);
        })
        .then()

这就引出了一个问题:为什么连接没有正常终止?服务器端遗漏了什么?此外,“订阅”不能与“ReactorNettyWebSocketClient”一起使用,因为他们设计API的方式不同。看到这是因为服务器在初始流结束时不调用
close()
…().delayElements(…)。然后(session::close)或者更确切地说,我可以编写'delayElements(…).doOnComplete{session.close()}'或'delayElements(…).doFinally{session.close()}',但这两种代码都不会影响输出。对“.then(…)”的调用不会在该位置编译。这就引出了一个问题:为什么连接没有正常终止?服务器端遗漏了什么?此外,“订阅”不能与“ReactorNettyWebSocketClient”一起使用,因为他们设计API的方式不同。看到这是因为服务器在初始流结束时不调用
close()
…().delayElements(…)。然后(session::close)或者更确切地说,我可以编写'delayElements(…).doOnComplete{session.close()}'或'delayElements(…).doFinally{session.close()}',但这两种代码都不会影响输出。对“.then(…)”的调用不会在该位置编译。您可以使用
take()
运算符,因为您知道将接收多少项。您可以使用
take()
运算符,因为您知道将接收多少项。