Spring Boot 中使用 grpc 入門

Hans發表於2018-05-26

grpc 簡介

grpc 是什麼

grpc 是一個高效能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計。目前提供 C、Java 和 Go 語言版本,分別是:grpc, grpc-java, grpc-go. 其中 C 版本支援 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支援。

grpc 基於 HTTP/2 標準設計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 連線上的多複用請求等特。這些特性使得其在移動裝置上表現更好,更省電和節省空間佔用。

環境安裝

  • 首先安裝 protobuf :
    基於MAC環境,開啟終端,執行如下命令:

    brew install protobuf

    檢視是否安裝成功:

    protoc --version
  • 再編譯安裝 grpc-java 外掛:
    使用git下載原始碼:

    git clone https://github.com/grpc/grpc-java.git

    進入原始碼 compiler 目錄:

    cd compiler

    依次執行命令:

     ../gradlew java_pluginExecutable
     ../gradlew test
     ../gradlew install

    可能需要翻牆,並執行成功為止,最後會生成外掛 protoc-gen-grpc-java 。

使用示例

Maven 依賴

在服務端和客戶端的 pom.xml 中新增相關依賴:

<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-netty</artifactId>
  <version>1.12.0</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-protobuf</artifactId>
  <version>1.12.0</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-core</artifactId>
  <version>1.12.0</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-stub</artifactId>
  <version>1.12.0</version>
</dependency>

編寫服務端 proto 檔案

hello.proto

syntax = "proto3";

option java_multiple_files = false;
option java_package = "com.hans.grpcserver.grpc";
option java_outer_classname = "HelloProto";

package hello;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

使用 proto 工具生成 java 程式碼

protoc --java_out=. hello.proto

使用 grpc-java 工具生成 java 程式碼

注意外掛地址為以上編譯生成的實際地址

protoc --plugin=protoc-gen-grpc-java=/Users/huangxun/workspace/java/grpc-java/compiler/build/exe/java_plugin/protoc-gen-grpc-java --grpc-java_out=. --proto_path=. hello.proto

編寫服務端 proto 檔案

修改 option java_package = “com.hans.grpcclient.grpc”; 後再次執行以上命令則生成客戶端 java 程式碼

將程式碼放到對應目錄

即 com.hans.grpcserver 子目錄 grpc 下

編寫服務端

package com.hans.grpcserver.grpc.server;

import com.hans.grpcserver.grpc.GreeterGrpc;
import com.hans.grpcserver.grpc.HelloProto.HelloReply;
import com.hans.grpcserver.grpc.HelloProto.HelloRequest;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.io.IOException;
import java.util.logging.Logger;

public class HelloServer {
    private static final Logger logger = Logger.getLogger(HelloServer.class.getName());
    private int port = 50051;
    private Server server;

    private void start() throws IOException {
        // 使用ServerBuilder來構建和啟動服務,通過使用forPort方法來指定監聽的地址和埠
        // 建立一個實現方法的服務GreeterImpl的例項,並通過addService方法將該例項納入
        // 呼叫build() start()方法構建和啟動rpcserver
        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");
                HelloServer.this.stop();
                System.err.println("*** server shut down");
            }
        });
    }

    private void stop() {
        if (server != null) {
            server.shutdown();
        }
    }

    /**
     * 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 HelloServer server = new HelloServer();
        server.start();
        server.blockUntilShutdown();
    }

    // 我們的服務GreeterImpl繼承了生成抽象類GreeterGrpc.GreeterImplBase,實現了服務的所有方法
    private class GreeterImpl extends GreeterGrpc.GreeterImplBase {

        @Override
        public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
            // 使用響應監視器的onNext方法返回HelloReply
            responseObserver.onNext(reply);
            // 使用onCompleted方法指定本次呼叫已經完成
            responseObserver.onCompleted();
        }
    }
}

編寫客戶端

package com.hans.grpcclient.grpc.client;

import com.hans.grpcclient.grpc.GreeterGrpc;
import com.hans.grpcclient.grpc.HelloProto.HelloReply;
import com.hans.grpcclient.grpc.HelloProto.HelloRequest;
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;

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

    private final ManagedChannel channel;
    private final GreeterGrpc.GreeterBlockingStub blockingStub;

    /** Construct client connecting to HelloWorld server at {@code host:port}. */
    // 首先,我們需要為stub建立一個grpc的channel,指定我們連線服務端的地址和埠
    // 使用ManagedChannelBuilder方法來建立channel
    public HelloClient(String host, int port) {
        channel = ManagedChannelBuilder.forAddress(host, port)
                // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
                // needing certificates.
                .usePlaintext(true)
                .build();
        // 使用我們從proto檔案生成的GreeterGrpc類提供的newBlockingStub方法指定channel建立stubs
        blockingStub = GreeterGrpc.newBlockingStub(channel);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }
    // 呼叫服務端方法
    /** Say hello to server. */
    public void greet(String name) {
        logger.info("Will try to greet " + name + " ...");
        // 建立並定製protocol buffer物件,使用該物件呼叫服務端的sayHello方法,獲得response
        HelloRequest request = HelloRequest.newBuilder().setName(name).build();
        HelloReply response;
        try {
            response = blockingStub.sayHello(request);
            // 如果有異常發生,則異常被編碼成Status,可以從StatusRuntimeException異常中捕獲
        } 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.
     */
    public static void main(String[] args) throws Exception {
        HelloClient client = new HelloClient("localhost", 50051);
        try {
            /* Access a service running on the local machine on port 50051 */
            String user = "hans";
            if (args.length > 0) {
                user = args[0]; /* Use the arg as the name to greet if provided */
            }
            client.greet(user);
        } finally {
            client.shutdown();
        }
    }
}

啟動服務端

後臺列印輸出:

Server started, listening on 50051

執行客戶端

後臺列印輸出:

Will try to greet hans ...
Greeting: Hello hans

Github 完整程式碼

https://github.com/hxun123/sp…

相關文章