Source Code: https://github.com/joexu01/rsocket-demo

0. 四种通信模式

让我们来简单复习一下 RSocket 的四种通信模式:

  • 即发即忘 - FireAndForget:立即发送一个请求,无需为这个请求发送响应报文。适用于监控埋点,日志上报等,这种场景下无需回执,丢失几个请求无伤大雅

  • 请求响应 - RequestResponse:请求方发送一条请求消息,响应方收到请求后并返回一条响应消息。传统的HTTP是典型的Request-Response

  • 流式响应 - RequestStream:请求方发送一个请求报文,响应方发回N个响应报文。传统的MQ是典型的RequestStream

  • 双向通道 - Channel:创建一个通道上下文,双方可以互相发送消息。IM是个典型的RequestChannel通讯场景

1. 客户端生成和解析路由信息

*本篇文章的客户端示例文件在 rsocket-client-raw/src/main/java/org/example/FourCommunicationScheme.java

我们使用 decodeRouteencodeRoute 函数来解码和编码路由信息。

static String decodeRoute(ByteBuf metadata) {
final RoutingMetadata routingMetadata = new RoutingMetadata(metadata); return routingMetadata.iterator().next();
}
static ByteBuf encodeRoute(String route) {
return TaggingMetadataCodec.createTaggingContent(
ByteBufAllocator.DEFAULT,
Collections.singletonList(route));
}

2. RequestResponse

服务端处理函数

在这里我们编写一个简单的 Handler,它的 Route 是 test.echo,它接收一个请求并返回请求 Payload 的 data 中的字符串。

@MessageMapping("test.echo")
public Mono<String> simplyEcho(String data) throws InterruptedException {
Thread.sleep(1500);
logger.info("[test.echo]Received echo string from client: {}", data);
return Mono.just(String.format("[test.echo]I received your string: %s. Thank you.", data));
}

注意,这里的参数也可以是 Mono<String> ,然后对 Mono 进行操作并返回。

客户端发送请求

  1. 生成 metadata 的 route 信息,然后将字符串和 metadata 放入 Payload
ByteBuf routeMetadata = encodeRoute("test.echo");
Payload echoPayload = ByteBufPayload.create(
ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "This is a message from client using rsocket-java library."),
routeMetadata);
  1. 新建一个 RequestResponse,并对这次请求做一些设置;可以看到 RequestResponse 方法返回的数据类型是 Mono<Payload>。然后我们对这个 Mono 设定一些操作(具体操作请看代码注释):
Mono<Payload> requestResponse = socket.requestResponse(echoPayload);
requestResponse
// 当 subscribe() 操作开始执行时打印一下日志
.doOnSubscribe(subscription -> logger.info("Test1 subscribed to {}", subscription.toString()))
// 当携带的请求成功后要做的事情
.doOnSuccess(payload -> {
logger.info("Test1 - Successfully returned: {}", payload.getDataUtf8());
payload.release();
})
.doOnError(throwable -> logger.info("Test1 doOnError: {}", throwable.toString()))
// 可以使用 timeout 丢弃等待超时的 Mono
//.timeout(Duration.ofSeconds(1))
// 可以使用 doOnTerminate 在请求结束后做一些工作
// .doOnTerminate(() -> {})
// 但是一定要设置 doOnError
//.doOnError(TimeoutException.class, e -> logger.info("Test1 doOnError: {}", e.toString()))
// .onErrorReturn(TimeoutException.class, DefaultPayload.create("Payload: Test1 - timeout"))
// 可以使用 log() 来观察数据的状态
//.log()
// 客户端在执行 subscribe() 操作时才会开始从服务端接收数据流
// 在响应式编程中使用 subscribe 操作符是订阅一个数据流并处理发布的数据、错误和完成信号的核心方式之一
.subscribe();

请求发出后主线程不会阻塞,所以我们需要使用 socket.onClose().block(); 保持连接。

然后我们尝试运行服务端和客户端,看看一看客户端的输出:

[main] INFO org.example.RSocketClientRaw - My UUID is 0718ef3b-9ee0-42f1-9003-700a8aa9a98d
[main] INFO org.example.RSocketClientRaw - Test1 subscribed to RequestResponseRequesterMono
[reactor-tcp-epoll-2] INFO org.example.RSocketClientRaw - Test1 - Successfully returned: [test.echo]I received your string: This is a message from client using rsocket-java library.. Thank you.

服务端日志:

2023-03-12 21:47:29.291  INFO 32099 --- [or-http-epoll-2] o.example.controller.RSocketController   : [connect.setup]Client connection: 0718ef3b-9ee0-42f1-9003-700a8aa9a98d
2023-03-12 21:47:32.304 INFO 32099 --- [or-http-epoll-2] o.example.controller.RSocketController : [test.echo]Received echo string from client: This is a message from client using rsocket-java library.

客户端成功地发出请求并收到来自服务端的回复。

3. FireAndForget

服务端

@MessageMapping("upload.log")
public void fireAndForgetHandler(@Headers Map<String, Object> header, RSocketRequester requester, String data) {
header.forEach((k, v) -> System.out.printf("[upload.log]header key: %s, val: %s\n", k, v));
System.out.printf("[upload.log]UploadEventLogs: Received log string from client: %s\n", data);
}

服务端接受一个请求,不返回任何结果(Fire'n'Forget),只在服务端打印 Header 的内容。

客户端

// 测试 FnF
routeMetadata = TaggingMetadataCodec.createTaggingContent(ByteBufAllocator.DEFAULT, Collections.singletonList("upload.log"));
socket.fireAndForget(
ByteBufPayload.create(
ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "This is a log from client using rsocket-java library."),
routeMetadata))
.doOnSubscribe(subscription -> logger.info("Test2 - Fire And Forget onSubscribe: {}", subscription.toString()))
.subscribe();

客户端输出:

[main] INFO org.example.RSocketClientRaw - Test2 - Fire And Forget onSubscribe: FireAndForgetRequesterMono

服务端输出:

2023-03-10 15:10:25.675  INFO 5318 --- [or-http-epoll-4] o.example.controller.RSocketController   : [test.echo]Received echo string from client: This is a message from client using rsocket-java library.
[upload.log]header key: dataBufferFactory, val: NettyDataBufferFactory (PooledByteBufAllocator(directByDefault: true))
[upload.log]header key: rsocketRequester, val: org.springframework.messaging.rsocket.DefaultRSocketRequester@607cc59
[upload.log]header key: lookupDestination, val: upload.log
[upload.log]header key: contentType, val: application/binary
[upload.log]header key: rsocketFrameType, val: REQUEST_FNF
[upload.log]UploadEventLogs: Received log string from client: This is a log from client using rsocket-java library.

4. RequestStream

服务端

服务端接收一个 Mono<String> 然后返回给客户端包含 10 个 StringFlux

事实上,严格按照响应式编程的策略,这里应该直接对 Mono 进行操作,可以使用 flatMapMany() 把生成的数据流通过异步方式处理,扩展出新的数据流。下面是扩展新数据流的简单示例:

Mono.just(3)
.flatMapMany(i -> Flux.range(0, i))
.subscribe(System.out::println);

在这里为了演示方便就先打印 Mono 然后新生成一个 Flux

@MessageMapping("handler.request.stream")
public Flux<String> responseStreaming(Mono<String> request) {
request
.doOnNext(s -> logger.info("[handler.request.stream]: {}", s))
// 可以使用 then() 结束操作链
.then()
.subscribe(); return Flux
.range(1, 10)
.map(idx -> String.format("Resp from Server: %s, Thank you!", idx));
}

客户端

请看代码注释来理解对数据流 Flux 的各种操作:

// 测试 RequestStream
routeMetadata = encodeRoute("handler.request.stream");
Flux<Payload> requestStream = socket.requestStream(
ByteBufPayload.create(
ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "TEST3 - Request&Stream"),
routeMetadata)); requestStream
// 当然可以使用 map 对每个 Payload 进行操作,这会改变数据
// .map(payload -> System.out.printf("%s\n", payload.getDataUtf8()))
.doOnSubscribe(subscription -> logger.info("Test3 subscribed to {}", subscription.toString()))
// 使用 doOnNext 不会对流的数据进行改变
// doOnNext()是一个 Reactor 式流操作符,它允许编写者注册一个在每次出现新元素时执行的回调函数
.doOnNext(nextPayload -> System.out.println("Test3 Received payload: " + nextPayload.getDataUtf8()))
// 当需要从流中选择一些特定的元素时,可以使用 Flux.take(long n) 操作符
// 该操作符将创建一个新的 Flux,该 Flux 包含原始 Flux 的前 n 个元素
// take 操作符发出了指定数量的元素之后,就不再接收任何元素,并且将取消其上游发布者的订阅
// 在这里服务端使用 Flux.range 来限定 Flux 流中的元素个数
// 如果服务端使用 Flux.interval 生成一个无限长度的流,客户端使用 take 接收限定个数的元素
// 便会取消发布者的订阅
.take(5)
.subscribe();

客户端输出结果:

[main] INFO org.example.RSocketClientRaw - My UUID is 28afc749-75e1-4289-8607-14810103de6c
[main] INFO org.example.RSocketClientRaw - Test3 subscribed to RequestStreamRequesterFlux
Test3 Received payload: Resp from Server: 1, Thank you!
Test3 Received payload: Resp from Server: 2, Thank you!
Test3 Received payload: Resp from Server: 3, Thank you!
Test3 Received payload: Resp from Server: 4, Thank you!
Test3 Received payload: Resp from Server: 5, Thank you!

服务端接收到了请求:

2023-03-12 22:01:33.520  INFO 32099 --- [or-http-epoll-3] o.example.controller.RSocketController   : [handler.request.stream]: TEST3 - Request&Stream

5. Channel

服务端

服务端接收来自客户端的整数字符串,将它们乘以2以后发送回去。我们不妨把处理客户端请求流的函数封装为一个 Spring Service:

@Service
public class MathService {
public Flux<String> doubleInteger(Flux<String> request) {
return request
.map(s -> {
System.out.println("received " + s);
int i = Integer.parseInt(s);
return String.valueOf(i * 2);
});
} }

编写处理函数:

@Autowired
private MathService mathService; @MessageMapping("handler.request.channel")
public Flux<String> responseChannel(Flux<String> payloads) {
return this.mathService.doubleInteger(payloads);
}

客户端

Flux<Payload> payloadFlux = Flux.range(-5, 10)
.delayElements(Duration.ofMillis(500))
.map(obj ->
{
ByteBuf metadata = encodeRoute("handler.request.channel");
return ByteBufPayload.create(
ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, obj.toString()), metadata);
}); Flux<Payload> channelResp = socket.requestChannel(payloadFlux);
channelResp
.doOnSubscribe(subscription -> logger.info("Test4 subscribed to {}", subscription.toString()))
.doOnError(throwable -> logger.info(throwable.toString()))
.doOnNext(nextPayload -> System.out.println("Test4 Received payload: " + nextPayload.getDataUtf8()))
.subscribe();

客户端输出:

[main] INFO org.example.RSocketClientRaw - My UUID is 96ff8fe7-416c-4607-9518-463114725a7a
[main] INFO org.example.RSocketClientRaw - Test4 subscribed to RequestChannelRequesterFlux
Test4 Received payload: -10
Test4 Received payload: -8
Test4 Received payload: -6
Test4 Received payload: -4
Test4 Received payload: -2
Test4 Received payload: 0
Test4 Received payload: 2
Test4 Received payload: 4
Test4 Received payload: 6
Test4 Received payload: 8

服务端输出:

2023-03-12 22:07:05.542  INFO 33083 --- [or-http-epoll-2] o.example.controller.RSocketController   : [connect.setup]Client connection: 96ff8fe7-416c-4607-9518-463114725a7a

received -5
received -4
received -3
received -2
received -1
received 0
received 1
received 2
received 3
received 4

下一篇文章会展示服务端如何主动调用客户端的函数。如有错误欢迎在评论区批评指正!

【RSocket】使用 RSocket(二)——四种通信模式实践的更多相关文章

  1. 十五天精通WCF——第六天 你必须要了解的3种通信模式

    wcf已经说到第六天了,居然还没有说到这玩意有几种通信模式,惭愧惭愧,不过很简单啦,单向,请求-响应,双工模式,其中的第二种“请求-响应“ 模式,这个大家不用动脑子都清楚,这一篇我大概来分析下. 一: ...

  2. 【转】JavaScript中的this关键字使用的四种调用模式

    http://blog.csdn.net/itpinpai/article/details/51004266 this关键字本意:这个.这里的意思.在JavaScript中是指每一个方法或函数都会有一 ...

  3. Android Activity 的四种启动模式 lunchMode 和 Intent.setFlags();singleTask的两种启动方式。

    原文:Android Activity 的四种启动模式 lunchMode 和 Intent.setFlags();singleTask的两种启动方式. Android Activity 的四种启动模 ...

  4. Activity的四种启动模式任务栈图解

    转载本专栏文章,请注明出处,尊重原创 .文章博客地址:道龙的博客 今天带来另一篇Activity的文章--Activity的四种启动模式.该篇文章,会以图文讲解的方式带你彻底掌握Activity的启动 ...

  5. [转帖]Docker四种网络模式

    Docker(十四)-Docker四种网络模式 https://www.cnblogs.com/zhuochong/p/10069293.html 计算机网络相关的知识 非常有用.. Docker 安 ...

  6. Android之Activity系列总结(三)--Activity的四种启动模式

    一.返回栈简介 任务是指在执行特定作业时与用户交互的一系列 Activity. 这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈,也叫任务栈)中. 首先介绍一下任务栈: (1)程序打开时 ...

  7. Android -- Activity的生命周期,Activity四种启动模式 Standard, SingleTop,SingleTask,SingleInstance

    1. 示例图 . 这七个方法定义了Activity的完整生命周期.实现这些方法可以帮助我们监视其中的三个嵌套生命周期循环:  Activity的完整生命周期自第一次调用onCreate()开始,直至调 ...

  8. thinkphp3.2笔记(2)调试模式,配置项C,创建模块, 四种URL模式,URL生成,跳转

    一.调试模式 TP的调试模式其实就控制了TP关于配置信息以及函数的缓存功能 如果开启了调试模式,每次访问项目,Tp都会去加载最新的配置以及函数信息. 如果关闭了调试模式,当tp第一次访问时会降配置以及 ...

  9. Open Interface Service WCF三种通信模式

    WCF三种通信模式 一.请求响应模式: 概念:客户端发送请求,一直等待服务端响应,在此期间处于等待(假死)状态:直到服务器响应,才能继续执行其他的操作: 即使返回值是void 也属于请求与答复模式. ...

  10. JavaScript (JS) 函数补充 (含arguments、eval()、四种调用模式)

    1. 程序异常 ① try-catch语法    测试异常 try-catch语法代码如下: try { 异常代码;     try中可以承重异常代码, console.log(“try”)  出现异 ...

随机推荐

  1. Software--Programming--Java__Maven

    Maven 是一个构建工具,可用于编译.测试和部署 Java 项目 采用了 管理优先配置原则. Maven 构建的项目的默认目录结构       1 <?xml version="1. ...

  2. 找着CA证书位置

    php -r "print_r(openssl_get_cert_locations());"

  3. cadence报错because the library part is newer than the part in the design cache.Select the part in the cache and choose Design-Update Cache,and then place the part again.

    cadence报错because the library part is newer than the part in the design cache.Select the part in the ...

  4. nmon 采坑

    1.安装 wget http://sourceforge.net/projects/nmon/files/nmon16g_x86.tar.gz 2.解压 tar -zxvf nmon16g_x86.t ...

  5. Arrays.asList()的使用

    JDK文档说明: @SafeVarargs public static <T> List<T> asList(T... a) 返回由指定数组支持的固定大小的列表(将返回的列表更 ...

  6. PDF转置

    import pdfplumber #全是None with pdfplumber.open(r"C:\Users\yjiang3\Desktop\Inv.pdf") as pdf ...

  7. uniapp中使用AntV F6 + table表格插件使用

    首先看页面效果: AntV官网下载F6文件到项目中与uViewUI插件 <template> <view class="page"> <!-- 导航栏 ...

  8. AXI4_LITE总线vivado2019.1官方模板源码(verilog实现)

    AXI lite总线读写时序 1. AXI_SLAVE源码 `timescale 1 ns / 1 ps module myip_v1_0_S00_AXI # ( // Users to add pa ...

  9. Chorme 兼容

    Chorme自动更新,不知道什么时候版本 就变了.之前好用的代码,突然不好用了. 启动时提示Only local connections are allowed 检查一下兼容性参照下文,下载对应的Dr ...

  10. Java8中Stream的用法

    Java8中Stream的用法 1.概述 Stream APl ( java.util.stream)把真正的函数式编程风格引入到Java中.这是目前为止对Java类库最好的补充,因为Stream A ...