gRPC簡單示例

gRPC概述

gRPC是一種跨語言的RPC框架,之所以它能跨語言,是因爲它基於protobuf描述對象實體和方法,最後通過protobuf編譯器生成指定語言的代碼。
這樣,就能通過一套protobuf聲明生成多種語言的相同API,對於實現跨語言的RPC通信非常便利,同時也使用protobuf作爲通信的序列化協議。

如下通過一個簡單的示例展示如何在Java語言中基於gRPC實現一個C/S架構的通信模型。

使用步驟

安裝protobuf編譯器

下載並安裝protobuf編譯器,並將其bin路徑添加到PATH變量中,如:D:\opt\protoc-3.13.0-win64\bin

添加protobuf-java依賴

在Maven項目中添加protobuf-java依賴:

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.13.0</version>
</dependency>

注:這裏protobuf-java的版本必須與protobuf編譯器的版本保持一致!

編寫protobuf描述文件

編寫protobuf描述文件(在Maven項目中通常將proto文件放在src/main/proto路徑下)

// hello_world.proto
syntax = "proto3";

option java_multiple_files = true;                         // 每個message類型是否生成獨立的文件
option java_outer_classname = "HelloWordProto";            // 當java_multiple_files=false時生成的多個message類的包裝類名
option java_package = "org.chench.extra.java.grpc.proto";  // 生成的java文件所在包名

message HelloRequest {                                     // 通過message聲明一個實體類
  string greeting = 1;                                     // 類對象屬性
}

message HelloResponse {
  string reply = 1;
}

service HelloService {                                     // 通過service聲明rpc類
  rpc SayHello (HelloRequest) returns (HelloResponse);     // rpc方法
}

編譯protobuf描述文件

編譯protobuf描述文件生成對應的Java類文件,有2種方式:

方式一:進入到protobuf描述文件路徑,執行命令:protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/xxx.proto(注:$SRC_DIR$DST_DIR都必須是絕對路徑,否則無法正確編譯)
注:命令行編譯的方式默認只會生成message聲明的實體類,不會生成service聲明的RPC類,解決辦法參考:protoc不生成.proto中的service,只生成model相關類,求助

方式二:通過Maven插件編譯:

<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.6.2</version>
        </extension>
    </extensions>
    <plugins>
        <!-- 使用如下插件編譯proto文件 -->
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.6.1</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:3.13.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>
                <!-- 指定proto文件位置 -->
                <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
                <!-- 指定proto文件編譯後生成的java文件位置 -->
                <outputDirectory>${project.basedir}/src/main/java</outputDirectory>
                <!--設置是否在生成java文件之前清空outputDirectory的文件,默認值爲true,設置爲false時也會覆蓋同名文件-->
                <clearOutputDirectory>false</clearOutputDirectory>
            </configuration>
            <executions>
                <execution>
                    <!--在執行mvn compile的時候會執行以下操作-->
                    <phase>compile</phase>
                    <goals>
                        <!--生成OuterClass類-->
                        <goal>compile</goal>
                        <!--生成Grpc類-->
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

在項目根目錄下執行:mvn compile即可生成對應的java類。
protobuf編譯生成Java類

簡單rpc示例

服務端

// HelloWorldServer.java
public class HelloWorldServer {
    private static int port = 8181;
    private Server server;

    public static void main(String[] args) throws IOException, InterruptedException {
        HelloWorldServer helloWorldServer = new HelloWorldServer();
        helloWorldServer.start();
        helloWorldServer.blockUntilShutdown();
    }

    private void start() throws IOException {
        this.server = ServerBuilder.forPort(port)
                .addService(new HelloServiceImpl())
                .build()
                .start();
        logger.info(String.format("start server on port: %s", port));
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                logger.info("do stop...");
                try {
                    HelloWorldServer.this.stop();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                logger.info("stop done.");
            }
        });
    }

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

    private void blockUntilShutdown() throws InterruptedException {
        if (this.server != null) {
            this.server.awaitTermination();
        }
    }

    // 服務端實現rpc接口
    class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
        @Override
        public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
            String greeting = request.getGreeting();
            logger.info(String.format("server receive greeting: %s", greeting));
            String responseMsg = new StringBuilder().append("Hello: ").append(greeting).toString();
            HelloResponse response = HelloResponse.newBuilder().setReply(responseMsg).build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }
    }
}

客戶端

// HelloWorldClient.java
public class HelloWorldClient {
    private HelloServiceGrpc.HelloServiceBlockingStub blockingStub;

    public HelloWorldClient(Channel channel) {
        this.blockingStub = HelloServiceGrpc.newBlockingStub(channel);
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        String target = "localhost:8181";
        ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
                .usePlaintext()
                .build();
        HelloWorldClient helloWorldClient = new HelloWorldClient(channel);
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String line = null;
        while ((line = reader.readLine()) != null) {
            if ("quit".equals(line.trim())) {
                channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
                System.exit(0);
            } else {
                helloWorldClient.greeting(line);
            }
        }
    }

    // 調用遠程RPC接口
    private void greeting(String greeting) {
        HelloRequest request = HelloRequest.newBuilder().setGreeting(greeting).build();
        HelloResponse response = this.blockingStub.sayHello(request);
        System.out.println(String.format("received response: %s", response.getReply()));
    }
}

【參考】
grpc-java
java使用protobuf-maven-plugin的插件編譯proto文件
java語言中生成gprc代碼的三種方式:gradle、protoc、鏡像的方式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章