基于RSocket协议实现客户端与服务端通信

RSocket基础开发demo

package com.pshdhx.rsocket;

import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.util.DefaultPayload;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Slf4j
public class MessageRSocketHandler implements RSocket {
    @Override
    public Mono<Void> fireAndForget(Payload payload) { //无响应 用于日志
        String message = payload.getDataUtf8(); //获取数据
        log.info("[fireAndForget]接收请求数据:{}",message);
        return Mono.empty();
    }

    @Override
    public Mono<Payload> requestResponse(Payload payload) { //传统模式 有请求有响应
        String message = payload.getDataUtf8(); //获取数据
        log.info("[RequestAndResponse]接收请求数据:{}",message);
        return Mono.just(DefaultPayload.create("[echo]"+message));
    }

    @Override
    public Flux<Payload> requestStream(Payload payload) { //处理流数据
        String message = payload.getDataUtf8(); //获取数据
        log.info("[RequestStream]接收请求数据:{}",message);
        return Flux.fromStream(message.chars()          //将接收到的字符串转换为int型数据流
        .mapToObj(c->Character.toUpperCase(c)) //将里边的每一个字符编码大写
        .map(Object::toString)//将字符转为String
        .map(DefaultPayload::create)); //创建payload附加数据
    }

    @Override
    public Flux<Payload> requestChannel(Publisher<Payload> publisher) { //双向流

        return Flux.from(publisher).map(Payload::getDataUtf8).map(msg->{
            log.info("【RequestChannel】接收请求数据:{}",msg);
            return msg;
        }).map(DefaultPayload::create);
    }

    @Override
    public Mono<Void> metadataPush(Payload payload) {
        return null;
    }

    @Override
    public Mono<Void> onClose() {
        return null;
    }

    @Override
    public void dispose() {

    }
}

RSocket服务器开发

package com.pshdhx.controller;

import com.pshdhx.message.MessageService;
import com.pshdhx.vo.Message;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;

@Controller
//不使用rest
@Slf4j
public class MessageFluxController {
    @Autowired
    private MessageService messageService;

    @MessageMapping("message.echo")
    public Mono<Message> echoMessage(Mono<Message> message){
        return message.doOnNext(msg->this.messageService.echo(msg)) //响应处理
        .doOnNext(msg->log.info("消息接收{}",message));
    }

    @MessageMapping("message.delete")
    public void deleteMessage(Mono<String> title){
        title.doOnNext(msg->log.info("消息删除{}",msg)).subscribe();

    }
    @MessageMapping("message.list")
    public Flux<Message> listMessage(){
        return Flux.fromStream(this.messageService.list().stream());
    }

    @MessageMapping("message.get")
    public Flux<Message> getMessage(Flux<String> title){
        return title.doOnNext(t->log.info("消息查询{}",t))
                .map(titleInfo->titleInfo.toLowerCase())
                .map(this.messageService::get)
                .delayElements(Duration.ofSeconds(1));
    }
}

RSocket客户端开发

配置策略和通信

package com.pshdhx.config;

import io.rsocket.RSocket;
import io.rsocket.transport.netty.client.TcpClientTransport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.codec.cbor.Jackson2CborDecoder;
import org.springframework.http.codec.cbor.Jackson2CborEncoder;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;

import java.time.Duration;

@Configuration
public class RSocketConfig {

    /**
     * 配置策略,编码和解码
     */
    @Bean
    public RSocketStrategies getRSocketStrategies(){
        return RSocketStrategies.builder()
                .encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
                .decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
                .build();
    }

    /**
     * 配置RSocket连接策略
     */
    @Bean
    public Mono<RSocketRequester> getRSocketRequester(RSocketRequester.Builder builder){
        return Mono.just(
          builder.rsocketConnector(rSocketConnector -> rSocketConnector.reconnect(
                  Retry.fixedDelay(2, Duration.ofSeconds(2))))
                    .dataMimeType(MediaType.APPLICATION_CBOR)
                    .transport(TcpClientTransport.create(6869))
        );
    }

}

客户端模拟调用

import com.pshdhx.AppClient;
import com.pshdhx.vo.Message;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import reactor.core.publisher.Mono;

@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@SpringBootTest(classes = AppClient.class)
public class TestRSocketClient {
    @Autowired
    private Mono<RSocketRequester> requesterMono; //来进行服务调用

    @Test
    public void testEchoMessage(){ //测试服务响应
        this.requesterMono.map(r->r.route("message.echo")
        .data(new Message("pshdhx","fighting")))
                .flatMap(r->r.retrieveMono(Message.class))
                .doOnNext(o-> System.out.println(o)).block();
    }

    @Test
    public void testDeleteMessage(){
        this.requesterMono.map(r->r.route("message.delete")
        .data("pshdhx"))
                .flatMap(RSocketRequester.RetrieveSpec::send).block();
    }

    @Test
    public void testListMessage(){
        this.requesterMono.map(r->r.route("message.list"))
                .flatMapMany(r->r.retrieveFlux(Message.class))
                .doOnNext(o->System.out.println(o)).blockLast();
    }

}

RSocket实现文件上传

RSocket协议由于本身是基于二进制传输,所以也提供了方便文件上传的处理支持。而在进行文件上传时,并不是将一个文件整体上传,而是采用了文件块(chunk)的形式进行上传文件的切割,利用Flux包裹要上传的一组文件块,而在服务器接收到该文件块之后,会通过专属的通道进行保存,同时也会将上传的状态发送到客户端。

 

 common模块

package com.pshdhx.type;

public enum  UploadStatus {
    CHUNK_COMPLETED, //文件上传处理之中
    COMPLETED, //文件上传完毕
    FAILED;//失败

}



package com.pshdhx.constants;

public class UploadConstants {
    public static final String MINE_FILE_NAME = "message/x.upload.file.name";
    public static final String MINE_FILE_EXTENSION = "message/x.upload.file.extension";
    public static final String FILE_NAME = "file.name";
    public static final String FILE_EXT = "file.ext";
}

服务器端:

package com.pshdhx.config;

import com.pshdhx.constants.UploadConstants;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.cbor.Jackson2CborDecoder;
import org.springframework.http.codec.cbor.Jackson2CborEncoder;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.util.MimeType;

@Configuration
public class RSocketServerConfig {
    @Bean
    public RSocketStrategies getRSocketStrategies(){
        return RSocketStrategies.builder()
                .encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
                .decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
                .metadataExtractorRegistry(metadataExtractorRegistry -> {
                    metadataExtractorRegistry.metadataToExtract(MimeType.valueOf(UploadConstants.MINE_FILE_NAME),String.class,UploadConstants.FILE_NAME);
                    metadataExtractorRegistry.metadataToExtract(MimeType.valueOf(UploadConstants.MINE_FILE_EXTENSION),String.class,UploadConstants.MINE_FILE_EXTENSION);
                })
                .build();
    }
}
@Controller
//不使用rest
@Slf4j
public class MessageFluxController {
    @Autowired
    private MessageService messageService;

    @Value("${output.file.path.upload}")
    private Path outputPath;
    @MessageMapping("message.upload")
    public Flux<UploadStatus> upload(
            @Headers Map<String,Object> metadata,
            @Payload Flux<DataBuffer> content
    ) throws  Exception{
        log.info("【上传后路径】outputPaht={}",this.outputPath);
        var fileName = metadata.get(UploadConstants.FILE_NAME);
        var fileExt = metadata.get(UploadConstants.MINE_FILE_EXTENSION);
        var path = Paths.get(fileName+"."+fileExt);
        log.info("【文件上传】fileName={}、fileExt = {},path={}",fileName,fileExt,path);
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(
                this.outputPath.resolve(path),
                StandardOpenOption.CREATE, //文件创建
                StandardOpenOption.WRITE  //文件写入
        );//异步文件通道
        return Flux.concat(DataBufferUtils.write(content,channel)
        .map(s->UploadStatus.CHUNK_COMPLETED),Mono.just(UploadStatus.COMPLETED))
                .doOnComplete(()->{
                    try {
                        channel.close();
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                })
                .onErrorReturn(UploadStatus.FAILED);

    }

客户端:

    @Value("classpath:/images/pic.jpg")
    private Resource resource;


 @Test
    public void testUpload(){
        String fileName = "pshdhx"+ UUID.randomUUID();
        String fileExt = this.resource.getFilename().substring(this.resource.getFilename().lastIndexOf(".")+1);
        Flux<DataBuffer> resourceFlux = DataBufferUtils.read(this.resource,new DefaultDataBufferFactory(),1024)
                                                .doOnNext(s-> System.out.println("文件上传:"+s));
        Flux<UploadStatus> uploadStatusFlux = this.requesterMono
                .map(r->r.route("message.upload")
                .metadata(metadataSpec -> {
                    System.out.println("【上传测试:】文件名称"+fileName+"."+fileExt);
                    metadataSpec.metadata(fileName, MimeType.valueOf(UploadConstants.MINE_FILE_NAME));
                    metadataSpec.metadata(fileExt, MimeType.valueOf(UploadConstants.MINE_FILE_EXTENSION));
                }).data(resourceFlux)).flatMapMany(r->r.retrieveFlux(UploadStatus.class))
                .doOnNext(o-> System.out.println("上传进度:"+o));
        uploadStatusFlux.blockLast();
    }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Modbus TCP和Socket是两种不同的通信方式。 Modbus TCP是基于Modbus协议的一种通信方式,它使用TCP/IP协议进行数据传输。Modbus TCP通常用于在以太网上连接主从设备,其中主设备是Modbus TCP的客户端,从设备是Modbus TCP的服务器。Modbus TCP使用标准的Modbus协议格式进行数据交换,可以实现实时的数据传输和控制。 Socket是一种通信接口,用于在应用层和传输层之间进行数据传输。它提供了一组接口,用于描述IP地址和端口,可以通过Socket向网络发送请求或者应答网络请求。Socket可以使用不同的传输协议,如TCP或UDP,来实现不同的通信需求。 因此,Modbus TCP和Socket是不同层次的概念。Modbus TCP是一种基于Modbus协议通信方式,而Socket是一种通信接口,可以用于实现不同的通信协议,包括Modbus TCP。 #### 引用[.reference_title] - *1* *3* [机器人运动控制-socket通讯和Modbus通讯](https://blog.csdn.net/weixin_37801425/article/details/113924824)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [如何理解RS485,modbus,tcpip,socket等术语](https://blog.csdn.net/wgd0707/article/details/123081753)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值