<xmp id="63nn9"><video id="63nn9"></video></xmp>

<xmp id="63nn9"></xmp>

<wbr id="63nn9"><ins id="63nn9"></ins></wbr>

<wbr id="63nn9"></wbr><video id="63nn9"><ins id="63nn9"><table id="63nn9"></table></ins></video>

【RSocket】使用 RSocket(二)——四種通信模式實踐

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 進行操作并返回。事實上,如果嚴格按照響應式編程的策略,這里應該直接對 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

下一篇文章會展示服務端如何主動調用客戶端的函數。如有錯誤歡迎在評論區批評指正!

posted @ 2023-03-12 22:13  joexu01  閱讀(148)  評論(0編輯  收藏  舉報
人碰人摸人爱免费视频播放

<xmp id="63nn9"><video id="63nn9"></video></xmp>

<xmp id="63nn9"></xmp>

<wbr id="63nn9"><ins id="63nn9"></ins></wbr>

<wbr id="63nn9"></wbr><video id="63nn9"><ins id="63nn9"><table id="63nn9"></table></ins></video>