我在Kotlin为Spring WebFlux编写了一个测试客户端和服务器。客户端向服务器发送号码(例如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()
}
}
这是客户:
fun main(args: Array<String>) {
val uri = URI("ws://localhost:8080/numbers")
val client = ReactorNettyWebSocketClient()
println("How many numbers would you like?")
val input = Flux.just(readLine())
client.execute(uri) { session ->
session.send(input.map(session::textMessage))
.then(
session.receive()
.map(WebSocketMessage::getPayloadAsText)
.map { it.toInt() }
.reduce { a,b ->
println("Reduce called with $a and $b")
a + b
}
.doOnNext(::println)
.then()
)
.then()
}.block()
}
客户端成功接收数字,并调用reduce,如下所示:
用0和1
减少调用使用1和2减少调用
使用3和3减少调用
然而,永远不会达到对 doOnNext 的调用 - 大概是因为客户端不知道最后一个项目已被发送。我的问题是,我需要在客户端或服务器上添加哪些代码才能打印总数?
更新:关闭服务器端的会话无济于事。我试过了:
.delayElements(Duration.ofMillis(500))
.doOnComplete { session.close() }
还有:
.delayElements(Duration.ofMillis(500))
.doFinally { session.close() }
但是对客户的行为都没有任何影响。也没有在调用'send'后明确关闭会话:
.and(session.send(publisher))
.then()
.and { session.close() }
.then()
答案 0 :(得分:1)
从Mono<Void>
返回的WebSocketHandler
指示处理完成的时间,进而指示WebSocket连接应保持打开状态的时间。问题在于,返回的Mono<Void>
的两面将永远无法完成。
客户端使用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();
第二个问题是服务器的组成不正确。发送与通过.and
接收同时触发。相反,发送流应首先依赖于接收计数:
session.receive()
.map(WebSocketMessage::getPayloadAsText)
.flatMap(s -> {
logger.debug("About to send " + s + " numbers");
count.set(Integer.parseInt(s));
return session.send(publisher);
})
.then()
答案 1 :(得分:0)
从marble diagram for reduce
可以看出,它希望上游发布者发送onComplete
事件来发送事件本身。我不确定,但我认为仅在连接正常终止时才会发出。这就是doOnNext
永远不会被执行的原因。
而不是reduce
我认为您应该使用subscribe()
代替,并定义您自己的Subscriber
实例,您可以保留状态。
编辑:您无法在此处使用subscribe
方法。如果在服务器上关闭连接,它应该像这样工作:
.map(Int::toString)
.map(session::textMessage)
.delayElements(Duration.ofMillis(500))
.then(session::close)