RPC框架之gRPC简单使用

前言

gRPC是一个谷歌推出的高性能的RPC框架,遵从server/client模型,可以使client调用server的接口就像调用本地方法一样简单,gRPC使用ProtoBuf来定义数据类型和接口,相比RestAPI,传输数据更少,性能更高。

下载proto编译器

不仅需要下载protoc编译器,使用gRPC还需要下载grpc-protoc插件(Java需要),这里是windows版本。

定义proto文件

复制syntax = "proto3";

package tutorial;

option java_package = "com.imooc.sourcecode.java.google.grpc.test1";
option java_outer_classname = "PersonProto";
option java_generic_services = true;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Sends another greeting
  rpc SayHelloAgain (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;
}

例子来源于官方文档
service 用来定义服务接口,可以看做Java中的接口。

使用Java编写服务器端和客户端代码

添加maven依赖

复制<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-netty-shaded</artifactId>
  <version>1.45.1</version>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-protobuf</artifactId>
  <version>1.45.1</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-stub</artifactId>
  <version>1.45.1</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
  <groupId>org.apache.tomcat</groupId>
  <artifactId>annotations-api</artifactId>
  <version>6.0.53</version>
  <scope>provided</scope>
</dependency>

根据proto文件创建Java数据类型和接口代码

有两种方式,一种是使用命令行,另一种是使用maven插件

命令行

复制protoc --proto_path=$SRC_DIR --plugin=$P_NAME=$P_DIR --grpc-java_out=$DST_DIR --java_out=$DST_DIR person.proto

--proto_path(也可以简写为-I)表示查找proto文件的路径,
--plugin表示要添加的执行插件,格式为name(插件名称)=dir(路径),
--grpc-java_out表示生成服务接口的路径,
--java_out表示生成数据类型的路径,
实际命令为

复制.\protoc.exe --proto_path=D:\java\code_resp\github_resp\source_code\src\main\java\com\imooc\sourcecode\java\google\grpc\test1 --plugin=protoc-gen-grpc-java=D:\java\software\protoc-3.20.0-win64\bin\protoc-gen-grpc-java-1.4.0-windows-x86_64.exe --grpc-java_out=D:\java\code_resp\github_resp\source_code\src\main\java --java_out=D:\java\code_resp\github_resp\source_code\src\main\java person.proto

--grpc-java_out和--java_out值不要包含具体的包路径,proto文件中已经配置了。

maven插件

复制<build>
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.4.1.Final</version>
    </extension>
  </extensions>
  <plugins>
    <plugin>
      <groupId>org.xolstice.maven.plugins</groupId>
      <artifactId>protobuf-maven-plugin</artifactId>
      <version>0.5.0</version>
      <configuration>
        <protocArtifact>com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.4.0:exe:${os.detected.classifier}</pluginArtifact>
        <clearOutputDirectory>false</clearOutputDirectory>
        <protoSourceRoot>${basedir}/src/main/java/com/imooc/sourcecode/java/google/grpc/test1</protoSourceRoot>
        <outputDirectory>${basedir}/src/main/java</outputDirectory>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

参考官方文档,自定义了三个属性,
clearOutputDirectory表示代码生成前是否清除输出目录,默认为true。
protoSourceRoot属性表示查找proto文件的路径,默认路径为${basedir}/src/main/proto。
outputDirectory属性表示生成文件的路径,不需要包含具体的包路径,proto文件中已经配置了,默认路径为${project.build.directory}/generated-sources/protobuf/java。
插件的更多属性可以查看Maven Protocol Buffers Plugin

protobuf:compile任务会生成HelloProto.java文件,protobuf:compile-custom任务会生成GreeterGrpc.java文件,插件底层也是通过命令行方式来实现的。

服务器端代码

复制import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloReply;
import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloRequest;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

/**
 * 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();
    }

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

核心在于GreeterImpl实现类,处理客户端的请求。

客户端代码

复制import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloReply;
import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloRequest;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A simple client that requests a greeting from the {@link HelloWorldServer}.
 */
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: " + 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 = "world";
    // Access a service running on the local machine on port 50051
    String target = "localhost:50051";
    // Allow passing in the user and target strings as command line arguments
    if (args.length > 0) {
      if ("--help".equals(args[0])) {
        System.err.println("Usage: [name [target]]");
        System.err.println("");
        System.err.println("  name    The name you wish to be greeted by. Defaults to " + user);
        System.err.println("  target  The server to connect to. Defaults to " + target);
        System.exit(1);
      }
      user = args[0];
    }
    if (args.length > 1) {
      target = args[1];
    }

    // 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);
    }
  }
}

客户端和服务器端代码都来自grpc-java

编写Python服务器端和客户端代码

安装依赖

复制pip install grpcio
pip install grpcio-tools

PyCharm中可以这样安装

根据proto文件创建Python数据类型和接口代码

复制python -m grpc_tools.protoc -I=$SRC_DIR --python_out=$DST_DIR --grpc_python_out=$DST_DIR person.proto

-m表示以脚本的方式运行python模块。
-I表示查找proto文件的路径,
--grpc_python_out表示生成服务接口代码的路径,
--python_out表示生成数据类型代码的路径,
实际命令为

复制.\python.exe -m grpc_tools.protoc -I=D:\java\code_resp\PycharmProjects\test_protobuf\grpc --python_out=D:\java\code_resp\PycharmProjects\test_protobuf\grpc --grpc_python_out=D:\java\code_resp\PycharmProjects\test_protobuf\grpc person.proto

服务器端代码

复制from concurrent import futures
import logging

import grpc
import person_pb2
import person_pb2_grpc


class Greeter(person_pb2_grpc.GreeterServicer):

    def SayHello(self, request, context):
        return person_pb2.HelloReply(message='Hello, %s!' % request.name)


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    person_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()


if __name__ == '__main__':
    logging.basicConfig()
    serve()

核心在于Greeter类,处理来自客户端的请求。

客户端代码

复制from __future__ import print_function

import logging

import grpc
import person_pb2
import person_pb2_grpc


def run():
    # NOTE(gRPC Python Team): .close() is possible on a channel and should be
    # used in circumstances in which the with statement does not fit the needs
    # of the code.
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = person_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(person_pb2.HelloRequest(name='you'))
    print("Greeter client received: " + response.message)


if __name__ == '__main__':
    logging.basicConfig()
    run()

客户端和服务器端代码都来自grpc

总结

通过上述测试,我们可以发现,gRPC可以跨语言通信,如Java作为服务器端,Python作为客户端。更多gRPC相关用法,请查看官方文档

参考

gRPC-官方文档
如何给老婆解释什么是RPC
使用maven插件生成grpc所需要的Java代码
java protobuf 生成grpc 代码
gRPC详解

posted @   strongmore  阅读(613)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
历史上的今天:
2021-04-12 java中StreamAPI的Collector原理分析
点击右上角即可分享
微信分享提示