gRPC 非官方教程

news2024/9/23 9:21:21

一、 简介

gRPC的定义:

  1. 一个高性能、通用的开源RPC框架
  2. 主要面向移动应用开发: gRPC提供了一种简单的方法来精确地定义服务和为iOS、Android和后台支持服务自动生成可靠性很强的客户端功能库。
  3. 基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发
  4. 支持众多开发语言

 

二、 简单rpc调用

主要流程:

  1. 创建maven项目
  2. 添加grpc依赖,protobuf依赖和插件
  3. 通过.proto文件定义服务
  4. 通过protocol buffer compiler插件生成客户端和服务端
  5. 通过grpc API生成客户端和服务端代码

1. 创建maven项目

添加pom依赖, 包含依赖包和生成基础类的插件。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>vip.sunjin</groupId>
    <artifactId>GrpcServer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <grpc.version>1.36.1</grpc.version>
        <protobuf.version>3.15.6</protobuf.version>
    </properties>


    <dependencies>

        <!-- protobuf -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>${protobuf.version}</version>
        </dependency>

        <!-- GRPC -->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc.version}</version>
        </dependency>

    </dependencies>
    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.36.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

2. 定义RPC服务数据结构 proto文件

创建一个文件夹src/main/proto/

创建一个helloworld.proto文件

syntax = "proto3";

option java_multiple_files = true;
option java_package = "vip.sunjin.examples.helloworld";
option java_outer_classname = "HelloWorldProto";


package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

3. 生成基础类

使用maven编译项目 生成基础类

生成后的类文件如下:

target
  classes
    vip
      sunjin
        examples
          helloworld
            GreeterGrpc.java
            HelloReply.java
            HelloReplyOrBuilder.java
            HelloRequest.java
            HelloRequestOrBuilder.java
            HelloWorldClient.java
            HelloWorldProto.java
            HelloWorldServer.java

GreeterGrpc封装基本的GRPC功能,后续的客户端和服务端都从这个类引申出来。

4. 创建服务端

服务端只需要指定一个端口号,然后暴露一个服务。

/**
 * Server that manages startup/shutdown of a {@code Greeter} server.
 */
public class HelloWorldServer {
  private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());

  private Server server;

  private void start() throws IOException {
    /* The port on which the server should run */
    int port = 50051;
    server = ServerBuilder.forPort(port)
        .addService(new GreeterImpl())
        .build()
        .start();
    logger.info("Server started, listening on " + port);
    Runtime.getRuntime().addShutdownHook(new Thread() {
      @Override
      public void run() {
        // Use stderr here since the logger may have been reset by its JVM shutdown hook.
        System.err.println("*** shutting down gRPC server since JVM is shutting down");
        try {
          HelloWorldServer.this.stop();
        } catch (InterruptedException e) {
          e.printStackTrace(System.err);
        }
        System.err.println("*** server shut down");
      }
    });
  }

  private void stop() throws InterruptedException {
    if (server != null) {
      server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
    }
  }

  /**
   * Await termination on the main thread since the grpc library uses daemon threads.
   */
  private void blockUntilShutdown() throws InterruptedException {
    if (server != null) {
      server.awaitTermination();
    }
  }

  /**
   * Main launches the server from the command line.
   */
  public static void main(String[] args) throws IOException, InterruptedException {
    final HelloWorldServer server = new HelloWorldServer();
    server.start();
    server.blockUntilShutdown();
  }

  static class GreeterImpl extends GreeterGrpc.GreeterImplBase {

    @Override
    public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
      HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
      responseObserver.onNext(reply);
      responseObserver.onCompleted();
    }
  }
}

5. 创建客户端

客户端需要指定调用服务的地址和端口号并且通过调用桩代码调用服务端的服务。

客户端和服务端是直连的。

public class HelloWorldClient {
  private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());

  private final GreeterGrpc.GreeterBlockingStub blockingStub;

  /** Construct client for accessing HelloWorld server using the existing channel. */
  public HelloWorldClient(Channel channel) {
    // 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to
    // shut it down.

    // Passing Channels to code makes code easier to test and makes it easier to reuse Channels.
    blockingStub = GreeterGrpc.newBlockingStub(channel);
  }

  /** Say hello to server. */
  public void greet(String name) {
    logger.info("Will try to greet " + name + " ...");
    HelloRequest request = HelloRequest.newBuilder().setName(name).build();
    HelloReply response;
    try {
      response = blockingStub.sayHello(request);
    } catch (StatusRuntimeException e) {
      logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
      return;
    }
    logger.info("Greeting Reply: " + response.getMessage());
  }

  /**
   * Greet server. If provided, the first element of {@code args} is the name to use in the
   * greeting. The second argument is the target server.
   */
  public static void main(String[] args) throws Exception {
    String user = "neil";
    // Access a service running on the local machine on port 50051
    String target = "localhost:50051";


    // Create a communication channel to the server, known as a Channel. Channels are thread-safe
    // and reusable. It is common to create channels at the beginning of your application and reuse
    // them until the application shuts down.
    ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
        // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
        // needing certificates.
        .usePlaintext()
        .build();
    try {
      HelloWorldClient client = new HelloWorldClient(channel);
      client.greet(user);
    } finally {
      // ManagedChannels use resources like threads and TCP connections. To prevent leaking these
      // resources the channel should be shut down when it will no longer be used. If it may be used
      // again leave it running.
      channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
    }
  }
}

6. 测试

先启动服务端代码 HelloWorldServer

然后执行客户端代码 HelloWorldClient

执行结果如下:

2月 18, 2023 9:44:17 上午 vip.sunjin.grpcclient.HelloWorldClient greet
信息: Will try to greet neil ...
2月 18, 2023 9:44:18 上午 vip.sunjin.grpcclient.HelloWorldClient greet
信息: Greeting Reply: I'm Grpc Server , Hello neil

三、 grpc服务端流

一般业务场景下,我们都是使用grpc的simple-rpc模式,也就是每次客户端发起请求,服务端会返回一个响应结果的模式。

但是grpc除了这种一来一往的请求模式外,还有流式模式。

服务端流模式是说客户端发起一次请求后,服务端在接受到请求后,可以以流的方式,使用同一连接,不断的向客户端写回响应结果,客户端则可以源源不断的接受到服务端写回的数据。

下面我们通过简单例子,来说明如何使用,服务端端流。

1. 定义RPC服务数据结构 proto文件

MetricsService.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "vip.sunjin.examples.helloworld";
option java_outer_classname = "MetricsServiceProto";


message Metric {
  int64 metric = 2;
}

message Average {
  double val = 1;
}

service MetricsService {
  rpc collectServerStream (Metric) returns (stream Average);
}

然后使用maven编译项目 生成基础类

2.创建服务端代码

服务实现类

public class MetricsServiceImpl extends MetricsServiceGrpc.MetricsServiceImplBase {
    private static final Logger logger = Logger.getLogger(MetricsServiceImpl.class.getName());
    /**
     * 服务端流
     * @param request
     * @param responseObserver
     */
    @Override
    public void collectServerStream(Metric request, StreamObserver<Average> responseObserver) {
        logger.info("received request : " +  request.getMetric());
        for(int i = 0; i  < 10; i++){
            responseObserver.onNext(Average.newBuilder()
                    .setVal(new Random(1000).nextDouble())
                    .build());
            logger.info("send to client");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        responseObserver.onCompleted();
    }

}

服务端Server启动类

public class MetricsServer {
    private static final Logger logger = Logger.getLogger(MetricsServer.class.getName());
    public static void main(String[] args) throws IOException, InterruptedException {
        int port = 50051;
//        //启动服务
        MetricsServiceImpl metricsService = new MetricsServiceImpl();
        Server server = ServerBuilder.forPort(port).addService(metricsService).build();
        server.start();

        logger.info("Server started, listening on " + port);

        server.awaitTermination();
    }
}

3.创建客户端代码

通过异步Stub 调用服务

public class MetricsClient {
    private static final Logger logger = Logger.getLogger(MetricsClient.class.getName());

    public static void main(String[] args) throws InterruptedException {
        int port = 50051;
//        //获取客户端桩对象
        ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:" + port).usePlaintext().build();
        MetricsServiceGrpc.MetricsServiceStub stub = MetricsServiceGrpc.newStub(channel);

        //发起rpc请求,设置StreamObserver用于监听服务器返回结果
        stub.collectServerStream(Metric.newBuilder().setMetric(1L).build(), new StreamObserver<Average>() {
            @Override
            public void onNext(Average value) {
                System.out.println(Thread.currentThread().getName() + "Average: " + value.getVal());
            }

            @Override
            public void onError(Throwable t) {
                System.out.println("error:" + t.getLocalizedMessage());
            }

            @Override
            public void onCompleted() {
                System.out.println("onCompleted:");

            }
        });

        channel.shutdown().awaitTermination(50, TimeUnit.SECONDS);
    }
}

代码最后要有等待并且关闭通道的操作。

4.测试

先启动服务端,再启动客户端后,可以看到StreamObserver的onNext方法会源源不断的接受到服务端返回的数据。

5,服务端流使用场景:

  • 客户端请求一次,但是需要服务端源源不断的返回大量数据时候,比如大批量数据查询的场景。
  • 比如客户端订阅服务端的一个服务数据,服务端发现有新数据时,源源不断的吧数据推送给客户端。

四、 grpc客户端流

客户端流模式是说客户端发起请求与服务端建立链接后,可以使用同一连接,不断的向服务端传送数据,等客户端把全部数据都传送完毕后,服务端才返回一个请求结果。

1. 定义RPC服务数据结构 proto文件

这里修改service的定义,其他不变。 MetricsService.proto

service MetricsService {
  rpc collectClientStream (stream Metric) returns (Average);
}

2.创建服务端代码

如上rpc方法的入参类型前添加stream标识 是客户端流,然后服务端实现代码如下:

public class MetricsServiceImpl extends MetricsServiceGrpc.MetricsServiceImplBase {
    private static final Logger logger = Logger.getLogger(MetricsServiceImpl.class.getName());
    /**
     * 客户端流
     *
     * @param responseObserver
     * @return
     */
    @Override
    public StreamObserver<Metric> collectClientStream(StreamObserver<Average> responseObserver) {
        return new StreamObserver<Metric>() {
            private long sum = 0;
            private long count = 0;

            @Override
            public void onNext(Metric value) {
                logger.info("value: " + value);
                sum += value.getMetric();
                count++;
            }

            @Override
            public void onError(Throwable t) {
                logger.info("severError:" + t.getLocalizedMessage());
                responseObserver.onError(t);
            }

            @Override
            public void onCompleted() {
                responseObserver.onNext(Average.newBuilder()
                        .setVal(sum / count)
                        .build());
                logger.info("serverComplete: ");
                responseObserver.onCompleted();
            }
        };
    }
}

如上代码,服务端使用流式对象的onNext方法不断接受客户端发来的数据,然后等客户端发送结束后,使用onCompleted方法,把响应结果写回客户端。

服务端启动类MetricsServer不需要修改

3.创建客户端代码

客户端调用服务需要使用异步的Stub.

public class MetricsClient2 {
    private static final Logger logger = Logger.getLogger(MetricsServer.class.getName());
    public static void main(String[] args) throws InterruptedException {
        int port = 50051;
        //1.创建客户端桩
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build();
        MetricsServiceGrpc.MetricsServiceStub stub = MetricsServiceGrpc.newStub(channel);

        //2.发起请求,并设置结果回调监听
        StreamObserver<Metric> collect = stub.collectClientStream(new StreamObserver<Average>() {
            @Override
            public void onNext(Average value) {
                logger.info(Thread.currentThread().getName() + "Average: " + value.getVal());
            }

            @Override
            public void onError(Throwable t) {
                logger.info("error:" + t.getLocalizedMessage());
            }

            @Override
            public void onCompleted() {
                logger.info("onCompleted:");

            }
        });

        //3.使用同一个链接,不断向服务端传送数据
        Stream.of(1L, 2L, 3L, 4L,5L).map(l -> Metric.newBuilder().setMetric(l).build())
                .forEach(metric -> {
            collect.onNext(metric);
            logger.info("send to server: " + metric.getMetric());
        });

        Thread.sleep(3000);
        collect.onCompleted();
        channel.shutdown().awaitTermination(50, TimeUnit.SECONDS);
    }
}

4.测试

先启动服务端,再启动客户端后,可以看到代码3会把数据1,2,3,4,5通过同一个链接发送到服务端, 然后等服务端接收完毕数据后,会计算接受到的数据的平均值,然后把平均值写回客户端。 然后代码2设置的监听器的onNext方法就会被回调,然后打印出服务端返回的平均值3。

5,客户端流使用场景:

  • 比如数据批量计算场景:如果只用simple rpc的话,服务端就要一次性收到大量数据,并且在收到全部数据之后才能对数据进行计算处理。如果用客户端流 rpc的话,服务端可以在收到一些记录之后就开始处理,也更有实时性。

五、grpc双向流

双向流意味着客户端向服务端发起请求后,客户端可以源源不断向服务端写入数据的同时,服务端可以源源不断向客户端写入数据。

1. 定义RPC服务数据结构 proto文件

这里修改service的定义,其他不变。 重新生成基础代码。 MetricsService.proto

service MetricsService {
  rpc collectTwoWayStream (stream Metric) returns (stream Average);
}

如上rpc方法的入参类型前添加stream标识, 返回参数前也添加stream标识 就是双向流,然后服务端实现代码如下:

双向流的代码和客户端流基本一样,只是双向流可以同时支持双向的持续写入。

2.创建服务端代码

将服务实现类进行修改用来测试双向流。

public void onNext(Metric value) {
    logger.info("value: " + value);
    sum += value.getMetric();
    count++;
    responseObserver.onNext(Average.newBuilder()
            .setVal(sum * 1.0/ count)
            .build());
}

如上代码,服务端使用流式对象的onNext方法不断接受客户端发来的数据, 然后 不断的调用参数中的流对象把响应结果持续的写回客户端。 实现了双向流式调用。

3.创建客户端代码

客户端代码主要是把onCompleted之前的线程等待时间加长,以便等待服务端持续的返回。

Thread.sleep(10000);
collect.onCompleted();

4.测试

先启动服务端,再启动客户端后,可以看到代码3会把数据1,2,3,4,5通过同一个链接发送到服务端, 然后等服务端接收数据后,会实时的计算接受到的数据的平均值,然后把平均值写回客户端。 然后代码2设置的监听器的onNext方法就会被回调,然后打印出服务端返回的平均值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/355666.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Visual Studio 高级调试-代码调试

概述编程圈子里隔三差五的就会有场“谁是最强IDE”之争&#xff0c;重要的是我们需要对使用的IDE有充分的了解&#xff0c;正所谓工欲善其事&#xff0c;必先利其器。本文主要讲述Visual Studio常用的调试技巧&#xff0c;包括多类型断点&#xff0c;数据监视&#xff0c;以及多…

剑指 Offer 59 - I. 滑动窗口的最大值

摘要 剑指 Offer 59 - I. 滑动窗口的最大值 一、大顶堆求解 对于每个滑动窗口&#xff0c;我们可以使用 O(k) 的时间遍历其中的每一个元素&#xff0c;找出其中的最大值。对于长度为n的数组 nums而言&#xff0c;窗口的数量为 n−k1&#xff0c;因此该算法的时间复杂度为 O(…

在线图片转gif怎么操作?试试这一招在线制作gif

静图怎么变成gif动图&#xff1f;想要将手中的静态图片变成gif动图又不想下载软件的时候要怎么操作呢&#xff1f;很简单&#xff0c;通过使用【GIF中文网】的动图在线制作&#xff08;https://www.gif.cn/&#xff09;功能&#xff0c;两招就能在线制作gif图片&#xff0c;方便…

小红书购物笔记在哪里看?

小红书购物笔记在哪里看&#xff1f;#小红书带货#小红书变现#小红书运营#小红书营销#内容营销 在现在这个时代&#xff0c;网上购物已经成为一种日常的行为。每天大量的人在通过小红书购买他们心仪的商品&#xff0c;生活态度也越来越追求高品质。小红书不但能够让用户在网上购…

Metasploit框架基础(二)

文章目录前言一、Meatsplooit的架构二、目录结构datadocumentationlibmodulesplugins三、Measploit模块四、Metasploit的使用前言 Metasploit是用ruby语言开发的&#xff0c;所以你打开软件目录&#xff0c;会发现很多.rb结尾的文件。ruby是一门OOP的语言。 一、Meatsplooit的…

【opencv源码解析0.1】opencv库VS环境配置

opencv环境配置 感谢大家学习这门教程。本系列文章首发于公众号【周旋机器视觉】。 这个这门课程的第一篇文章&#xff0c;主要是opencv环境配置。 本教程的环境为 Visual Studio 2019CMake 3.22.3opencv 4.6.0windows 10 1、opencv的源码下载与安装 直接访问opencv官网&…

MySQL数据库优化————COUNT优化

直接进入主题 索引对count语句的影响 在我们对departments表进行count查询时&#xff0c;使用了以下语句 select count(*) from employees;当前employees表索引情况如图 只有一个主键索引 执行 explain select count(*) from employees;从结果中可以看到&#xff0c;这时…

NOIP2014-提高组初赛C语言解析(选择填空题)

第二十届(2014年)全国青少年信息学奥林匹克联赛初赛一、单项选择题&#xff08;共 20 题&#xff0c;每题 1.5 分&#xff0c;共计 30 分。每题有且仅有一个正确选项&#xff09;1. 以下哪个是面向对象的高级语言&#xff08; B &#xff09;A.汇编语言 B.C C.Fortran D.Basic参…

【opencv源码解析0.4】如何使用cmake来管理项目

如何使用cmake来管理项目 【opencv源码解析0.1】VS如何优雅的配置opencv环境 【opencv源码解析0.2】如何编译opencv库源码 【opencv源码解析0.3】调试opencv源码以及使用cmake来管理项目 前面几篇文章我们都是围绕Visual Studio 2019这个IDE来展开的&#xff0c;IDE为我们做了…

矩阵中的路径-剑指Offer-java深度优先

一、题目描述给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平相…

k8s部署mysql+初始化数据

1. 准备工作 1.k8s的前置内容需要提前了解 2.mysql的初始化数据 3.docerk相关知识点需要了解 2. 部署步骤 初始化数据文件准备&#xff0c;准备了nacos的一张表sql脚本&#xff0c;需要修改一点点内容 文件名称&#xff1a;init-nacos.sql 部分内容显示&#xff1a; 主要创建…

潘长江张杰再现狂飙名场面,一般人把握不住

潘长江张杰再现狂飙名场面&#xff0c;一般人把握不住#我们的客栈#高启强#老墨我饿了 在昨晚播出的《我们的客栈》在猜人游戏的环节中&#xff0c;张杰和潘长江商量了一番&#xff0c;决定还原《狂飙》某一名场面&#xff0c;没想到被张维伊一秒猜出&#xff0c;张维伊还说这是…

图像分类竞赛进阶技能:OpenAI-CLIP使用范例

OpenAI-CLIP 官方介绍 尽管深度学习已经彻底改变了计算机视觉&#xff0c;但目前的方法存在几个主要问题:典型的视觉数据集是劳动密集型的&#xff0c;创建成本高&#xff0c;同时只教授一组狭窄的视觉概念;标准视觉模型擅长于一项任务且仅擅长于一项任务&#xff0c;并且需要大…

【QT专栏】QT中实现多线程的四种方式总结(金针菇般细)

目录 一、继承QThread 1&#xff0c;基本概念 2&#xff0c;操作流程 二、继承QObject&#xff08;推荐&#xff09; 1&#xff0c;基本概念 2&#xff0c;操作流程 三、继承QRunnable&#xff0c;配合QThreadPool实现多线程 1&#xff0c;外界通信 2&#xff0c;QMet…

SpringSecurity的安全认证的详解说明(附完整代码)

SpringSecurity登录认证和请求过滤器以及安全配置详解说明 环境 系统环境&#xff1a;win10 Maven环境&#xff1a;apache-maven-3.8.6 JDK版本&#xff1a;1.8 SpringBoot版本&#xff1a;2.7.8 根据用户名密码登录 根据用户名和密码登录&#xff0c;登录成功后返回Token数据…

狂神聊Redis复习笔记一

目录目前一个基本的互联网项目&#xff01;NoSQL 特点Redis 是什么&#xff1f;Redis 能干嘛&#xff1f;特性测试性能基础的知识Redis 是单线程的&#xff01;Redis 为什么单线程还这么快&#xff1f;五大数据类型Redis-KeyString&#xff08;字符串&#xff09;List&#xff…

[软件工程导论(第六版)]第3章 需求分析(复习笔记)

文章目录3.1 需求分析的任务3.2 与用户沟通获取需求的方法3.3 分析建模与规格说明3.4 实体-联系图&#xff08;E-R图&#xff09;3.5 数据规范化3.6 状态转换图3.7 其他图形工具3.8 验证软件需求需求分析是软件定义时期的最后一个阶段&#xff0c;需求分析的基本任务是准确的回…

EASYui+C#web

第一步创建一个web应用程序。 选择web应用程序。 第二步选择mvc框架 创建完成项目目录。 如图引入easyui包。 记住复制到content文件夹&#xff0c;否则无法识别。 easyui下载&#xff0c;官网。 如何用 引入jscss文件 <link rel"stylesheet" type"text…

Guitar Pro8手机电脑免费版吉他软件下载

Guitar Pro8是专业的吉他软件&#xff0c;具有可视化的五线谱编辑器&#xff0c;涵盖常用的乐器和特殊乐器单元&#xff0c;内置海量吉他音色效果和1000多个乐器音色&#xff0c;成为一个小型音乐站&#xff0c;制作出动听的音乐&#xff0c;支持边看边听&#xff0c;添加音频轨…

【论文阅读】 Few-shot object detection via Feature Reweighting

Few-shot object detection的开山之作之一 ~~ 特征学习器使用来自具有足够样本的基本类的训练数据来 提取 可推广以检测新对象类的meta features。The reweighting module将新类别中的一些support examples转换为全局向量&#xff0c;该全局向量indicates meta features对于检…