gRPC使用

675 阅读5分钟

test

前言

网上有很多,尽可能的详细写

官方翻译文档: doc.oschina.net/grpc?t=5683…
官方文档: www.grpc.io/docs/langua…

本文讲述如何创建并使用gRPC(java)

环境

  • jdk1.8(要求jdk7及以上)
  • maven3.5

实测 jdk6 高版本也可以
如何运行在jdk1.6环境下
grpc version 1.14.0
protobuf.version 3.3.0

新建一个proto文件

在src/main下新建proto文件夹
新建helloworld.proto 内容如下:

syntax = "proto3";

option java_package = "com.meizi.demo.grpc.examples";

package helloworld;

// The greeter service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc BiTalk(stream StreamRequest) returns (stream StreamResponse) {}
}

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

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

message StreamRequest {
    string request_info = 1;
}

message StreamResponse {
    string response_info = 1;
}

关键词解释:

option java_package 指定我们java代码生成的包名,其他语言该参数无效
service 定义一个服务
rpc 定义一个rpc方法
stream 定义该类型为流类型
message 定义请求和响应的类型

这里我们在service里定义了2个rpc方法SayHelloBiTalk,分别对应下文的简单rpc双向流rpc

使用.proto文件生成java代码

maven的pom文件添加如下插件和依赖

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

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.42.1</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.42.1</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-okhttp</artifactId>
            <version>1.42.1</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>1.42.1</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty</artifactId>
            <version>1.42.1</version>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.19.1</version>
        </dependency>
    </dependencies>

执行 maven compile

[INFO] --- protobuf-maven-plugin:0.6.1:compile (default) @ test ---
[INFO] Compiling 1 proto file(s) to /Users/hlw/idea-workspace/test/target/generated-sources/protobuf/java
[INFO] 
[INFO] --- protobuf-maven-plugin:0.6.1:compile-custom (default) @ test ---
[INFO] Compiling 1 proto file(s) to /Users/hlw/idea-workspace/test/target/generated-sources/protobuf/grpc-java

生成2个文件,生成的文件提供给下面的客户端和服务端使用

pic.png

编写服务端代码

package com.****.demo.grpc.examples;

/*
 * Copyright 2015 The gRPC Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

/**
 * Server that manages startup/shutdown of a {@code Greeter} server.
 */
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();
    }

    /**
     * 覆盖GreeterImplBase的方法,否则客户端调用会报未实现的方法
     */
    static class GreeterImpl extends GreeterGrpc.GreeterImplBase {

        @Override
        public void sayHello(Helloworld.HelloRequest req, StreamObserver<Helloworld.HelloReply> responseObserver) {
            Helloworld.HelloReply reply = Helloworld.HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
            // 返回响应
            responseObserver.onNext(reply);
            // 表明已经完成处理
            responseObserver.onCompleted();
        }

        @Override
        public StreamObserver<Helloworld.StreamRequest> biTalk(StreamObserver<Helloworld.StreamResponse> responseObserver) {
            return new StreamObserver<Helloworld.StreamRequest>() {
                @Override
                public void onNext(Helloworld.StreamRequest streamRequest) {
                    System.out.println("server receive: "+streamRequest.getRequestInfo());
                    responseObserver.onNext(Helloworld.StreamResponse.newBuilder().setResponseInfo(UUID.randomUUID().toString()).build());
                }

                @Override
                public void onError(Throwable throwable) {
                    System.out.println(throwable.getMessage());
                }

                @Override
                public void onCompleted() {
                    responseObserver.onCompleted();
                }
            };
        }
    }
}

不要觉得上面代码乱,主要做了几件事

  1. 创建服务,停止服务的方法
  2. 创建一个类GreeterImpl来实现服务定义的方法

编写客户端代码

package com.****.demo.grpc.examples;

/*
 * Copyright 2015 The gRPC Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;

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;

    /**
     * 异步存根
     */
    private final GreeterGrpc.GreeterStub stub;

    StreamObserver<Helloworld.StreamRequest> requestStreamObserver;

    /** 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);
        stub = GreeterGrpc.newStub(channel);
    }

    /** Say hello to server. */
    public void greet(String name) {
        logger.info("Will try to greet " + name + " ...");
        Helloworld.HelloRequest request = Helloworld.HelloRequest.newBuilder().setName(name).build();
        Helloworld.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());
    }

    private void biTalk(){
        System.out.println("biTalk start");
        requestStreamObserver = stub.biTalk(new StreamObserver<Helloworld.StreamResponse>() {
            @Override
            public void onNext(Helloworld.StreamResponse streamResponse) {
                System.out.println("receive: "+streamResponse.getResponseInfo());
            }

            @Override
            public void onError(Throwable throwable) {
                System.out.println(throwable.getMessage());
            }

            @Override
            public void onCompleted() {
                System.out.println("my onComplated");
            }
        });
        System.out.println("biTalk end");
    }

    private void sendMsg(String s){
        System.out.println("send:" + s);
        requestStreamObserver.onNext(Helloworld.StreamRequest.newBuilder().setRequestInfo(s).build());
        //requestStreamObserver.onCompleted();
    }


    /**
     * 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 {
            System.out.println("-----------------------------");
            System.out.println("请求-响应,调用SayHello");

            HelloWorldClient client = new HelloWorldClient(channel);
            client.greet(user);

            System.out.println("-----------------------------");
            System.out.println("流式请求-流式响应,调用BiTalk");
            client.biTalk();
            for(int i = 0; i < 10; i++){
                client.sendMsg(String.valueOf(i));
                Thread.sleep(1000);
            }

            Thread.sleep(50000);

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

4种不同类型的方法

简单RPC

客户端向服务端发送一个请求,就像调用普通方法一样

rpc SayHello (HelloRequest) returns (HelloReply) {}

服务端流RPC

暂未实现

客户端流RPC

暂未实现

双向流RPC

双方使用读写流发送一系列消息。 这两个流独立运行,因此客户端和服务器可以按照他们喜欢的任何顺序进行读写:例如,服务器可以在写入响应之前等待接收所有客户端消息,或者它可以交替读取消息然后写入消息, 或其他一些读取和写入的组合。 保留每个流中消息的顺序。

rpc BiTalk(stream StreamRequest) returns (stream StreamResponse) {}

问题与解决

grpc相关问题

  1. call was half-closed
    问题产生:在回调oncompleted后,继续调动onnext
    原因:调用过onCompleted(),但现在正在调用onNext();那是不允许的

  2. io.grpc.statusruntimeexception:unavaliable:channel shutdownnow invoked
    调动ManagedChannel的onclose方法
    原因: 可能是调用mchannel.shutdownnow()方法,关闭了通道,但返回调用了oncompleted方法,该方法判断了状态,便抛出这个错

总结

偶碰到grpc相关的使用,故查阅相关文档总结在此。以上是一个初步的使用,更深入的研究挖掘,到官方文档寻找。

参考文献

  1. gRPC的通信方式-客户端流式、服务端流式、双向流式在Java的调用示例