Bootstrap

第三节 gRPC-Java 基础教程

目录

一、概述

二、route_guide.proto文件

三、生成代码(略)

四、代码详解

 4-1 服务端代码

4-2 工具类

4-3 客户端以及运行结果展示

4-3-1 客户端核心代码

4-3-2 简单RPC请求 演示

 4-3-3 服务端流式调用

 4-3-4 客户端流式请求

4-3-5 双向流式调用


一、概述

        本节内容介绍了Java语言使用gRPC的基本应用。通过本节内容,你除了可以复习.proto文件的创建、gRPC代码生成之外,您还将学习gRPC的四种调用方式:

        1> 简单gRPC调用(Unary RPC,一元RPC)

        2> 客户端流式调用

        3> 服务端流式调用

        4> 双向流式调用

        本示例是官方教程中的示例,只不过通过学习添加了自己的理解和中文注释而已。希望对大家有一定的帮助。

     (官方文档:https://www.grpc.io/docs/languages/java/basics/.)

        本示例使用一个简单的地图路径小程序,它可以允许客户端获取路线信息、指定范围内的路径概览、以及不同的客户端通过服务器交换响应的地图路径信息,并且通过这些示例将gRPC种四种调用模式进行介绍。

 示例代码:

git clone https://gitee.com/work_ldj/gRPC-examples.git

$cd gRPC-examples/RouteGuide-gRPC

目录结构如下:

二、route_guide.proto文件

syntax = "proto3";

option java_multiple_files = true;
option java_package = "top.joylee.example.grpc.route";
option java_outer_classname = "RouteGuideProto";

// 我们在service中定义rpc服务方法
// gRPC 允许我们以四种方式定义我们的rpc服务方法
// 本示例展示了这四种方式的应用
service RouteGuide {
    //第一种: A simple RPC
    //简单RPC调用,客户端通过stub发送请求,等待服务端响应;就像调用普通方法一样
    //业务说明:给定一个point,获取该point的feature值(比如这个点对应的地名)。
    //         如果服务端“数据库”种没有该点位,则feature中name设置为空字符串
    rpc GetFeature(Point) returns (Feature);

    //第二种:A server-side streaming RPC
    //服务端流式调用,客户端发送一条消息给服务端,服务端以流序列的方式响应多条消息客户端。
    //客户端读取返回的流消息,直到流中没有消息。
    //使用方法:在returns数据类型前,加上stream关键字即可
    //业务说明:获取指定范围内所有的特征点;rectangle指定了点位的上下左右界限。
    //         服务端是以流的方式返回给客户端,而不是一次性返回。
    //         因为指定范围(矩形)可能包含大量的特征点位
    rpc ListFeatures(Rectangle) returns (stream Feature);

    //第三种:A client-side streaming RPC
    //客户端流式调用,客户端以流的方式一次发送多条消息给服务端,等待服务端读取所有消息,并响应
    //使用方法:在请求方法数据类型前,加上stream即可
    //业务说明:接受一个点位流(可以简单的理解为列表)
    //         然后返回包含在“数据库”中的特征点位概览
    //         包含接收到的单个点的数量、检测到的特征的数量以及作为每个点之间距离的累积和所覆盖的总距离
    rpc RecordRoute(stream Point) returns (RouteSummary);

    //第四种:A bidirectional streaming RPC
    //双向流式rpc调用。 客户端和服务端都以读-写流的方式发送多条消息序列。
    //客户端和服务端的流相互独立运行,所以客户端和服务端按照他们各自喜欢的任何顺序进行读写。
    //例如:服务端可以等待读取完客户端所有的消息后,再响应
    //     或者交替读取或者写入消息;或者其他的读写组合。
    //保留每个流中的消息顺序。
    //使用方式:在请求和响应参数类型前均加上stream关键字
    //业务说明:以流的方式接受客户端的路径点位信息;同时,可以接受其他客户端发送的点位信息流;
    //         并同时响应
    rpc RouteChat(stream RouteNote) returns (stream RouteNote);
}

//点位
//采用E7表示法,点采用经纬度对
// (degrees multiplied by 10*7 and rounded to the nearest integer).
// latitude 表示经度,+/- 90度范围
// longitude 表示纬度,+/- 180度范围
message Point {
    int32 latitude = 1;
    int32 longitude = 2;
}

//经纬度矩形
//表示两个相对的点(对角线的点)
message Rectangle {
    Point lo = 1;
    Point hi = 2;
}

//给定点位的特征
//主要是point对象 + 该点的名称
//如果未指定点位名称,则名称为空
message Feature {
    string name = 1;
    Point location = 2;
}

//该消息并不是用来做RPC,而是为了进行磁盘文件与系统间的序列化
message FeatureDataBase {
    repeated Feature feature = 1;
}

//用来发送、回收一个point 信息
message RouteNote {
    Point location = 1;
    string message = 2;
}

//RecordRoute的汇总信息
//它包含接收到的单个点的数量、检测到的特征的数量以及作为每个点之间距离的累积和所覆盖的总距离
message RouteSummary {
    //点位数量
    int32 point_count = 1;
    //特征值数量
    int32 feature_count = 2;
    //点位总距离
    int32 distance = 3;
    //遍历所耗时间
    int32 elapsed_time = 4;
}

三、生成代码(略)

           写好.proto文件后,使用maven插件生成代码,并拷贝到响应的包。此处在“第二节 RPC-Java 快速开始”中已经讲明,此处不再赘述。

四、代码详解

 4-1 服务端代码

package top.joylee.example.grpc;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.joylee.example.grpc.route.*;

import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

//服务端程序
public class RouteGuideServer {
    private final static Logger log = LoggerFactory.getLogger(RouteGuideServer.class);

    private final int port;
    private final Server server;

    //构造函数
    //指定服务端监听端口号
    //同时,通过工具将json文件中定义好的点位特征值(Feature)加载到内存中来
    //RouteGuideUtil用来模拟数据库的查询操作,详见RouteGuideUtil代码
    public RouteGuideServer(int port) throws IOException {
        this(port, RouteGuideUtil.getDefaultFeaturesFile());
    }

    private RouteGuideServer(int port, URL featureFile) throws IOException {
        this(ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile));
    }

    RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features) {
        this.port = port;
        //使用ServerBuilder构建service
        //其中RouteGuideService类是继承的RouteGuideGrpc.RouteGuideImplBase
        //在该类中实现了.proto的四个方法
        server = serverBuilder.addService(new RouteGuideService(features))
                .build();
    }

    public static void main(String[] args) throws Exception {
        //第一步:创建一个服务端,指定监听端口
        RouteGuideServer server = new RouteGuideServer(18080);
        //第二步:启动服务端
        server.start();
        //第三步:阻塞,直到关闭服务
        server.blockUntilShutdown();
    }

    public void start() throws IOException {
        //如果server为空,证明创建server对象失败,无法启动
        if (Objects.isNull(server)) {
            log.error("route guide server start fail...");
            return;
        }
        //启动server
        server.start();
        log.info("Server started,listening {}......", port);

        //启动成功后,添加监听jvm关闭的钩子函数
        //如果JVM关闭,则关闭此服务端
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            log.warn("*** shutting down gRPC server  since JVM shutdown ***");
            try {
                stop();
            } catch (InterruptedException e) {
                log.error("await stop error,interrupted", e);
            }
            log.info("**** server shut down ,good bye ***");
        }));
    }

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

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

    //实现.proto文件中定义的rpc服务
    private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
        //用来保存所有“数据库中”存储的特征值
        //本示例中,会在new RouteGuideService 对象时通过工具,将json文件中的值加载进来
        private final Collection<Feature> features;

        //点位值对应的RouteNote对象列表映射
        private final ConcurrentMap<Point, List<RouteNote>> routeNotes =
                new ConcurrentHashMap<>();

        public RouteGuideService(Collection<Feature> features) {
            this.features = features;
        }


        /**
         * 查询point是否在“数据库”中有点位值
         *
         * @param request          point信息
         * @param responseObserver 返回Feature,执行完业务逻辑后,直接completed
         */
        @Override
        public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
            responseObserver.onNext(checkFeature(request));
            responseObserver.onCompleted();
        }

        /**
         * 获取指定返回内的特征点位值
         *
         * @param request          指定要查询的范围
         * @param responseObserver 服务端以流的方式响应客户端。
         */
        @Override
        public void listFeatures(Rectangle request, StreamObserver<Feature> responseObserver) {
            //左边界,取决于最小经度
            int left = Math.min(request.getLo().getLongitude(), request.getHi().getLongitude());
            //右边界,取决于最大经度
            int right = Math.max(request.getLo().getLongitude(), request.getHi().getLongitude());
            //上边界,取决于最大纬度
            int top = Math.max(request.getLo().getLatitude(), request.getHi().getLatitude());
            //下边界,取决于最小纬度
            int bottom = Math.min(request.getLo().getLatitude(), request.getHi().getLatitude());

            // 遍历“数据库”中所有的特征点位
            // 假设:这个过程会比较漫长,如果一次性的返回所有,则会等待很长时间才响应
            //      但是这里是以流的方式响应客户端,也就是在for循环中,找到一个就响应一个给客户端
            //      直到遍历结束才completed,结束服务端的响应
            for (Feature feature : features) {
                if (!RouteGuideUtil.exists(feature)) {
                    continue;
                }
                int lat = feature.getLocation().getLatitude();
                int lon = feature.getLocation().getLongitude();
                if (lon >= left && lon <= right && lat >= bottom && lat <= top) {
                    responseObserver.onNext(feature);
                }
            }
            responseObserver.onCompleted();
        }


        /**
         * 客户端流式调用
         *
         * @param responseObserver 服务端以流的方式将point列表发送给服务端
         * @return 服务端将提取流中的消息,并将收到的所有消息统计后,返回给客户端(服务端一次响应)
         */
        @Override
        public StreamObserver<Point> recordRoute(StreamObserver<RouteSummary> responseObserver) {
            //【特别注意】:由于客户端是以流的方式将多条消息(“消息列表”)请求过来,所以这里要建一个基于流请求的StreamObserver,去处理请求流
            return new StreamObserver<Point>() {
                int pointCount;
                int featureCount;
                int distance;
                Point previous;
                final long startTime = System.nanoTime();

                //处理请求流,来一个处理一个
                @Override
                public void onNext(Point point) {
                    pointCount++;
                    if (RouteGuideUtil.exists(checkFeature(point))) {
                        featureCount++;
                    }
                    if (Objects.nonNull(previous)) {
                        distance += calcDistance(previous, point);
                    }
                    previous = point;
                }

                @Override
                public void onError(Throwable throwable) {
                    log.error("record route cancel", throwable);
                }

                //请求流处理完成,则响应
                //只用响应单个请求体
                //至于这里的onCompleted方法,此方法是当客户端流完成传输后执行的方法。
                @Override
                public void onCompleted() {
                    long seconds = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime);
                    responseObserver.onNext(
                            RouteSummary.newBuilder()
                                    .setPointCount(pointCount)
                                    .setDistance(distance)
                                    .setFeatureCount(featureCount)
                                    .setElapsedTime((int) seconds)
                                    .build());
                    //此处是结束服务端响应的方法。
                    responseObserver.onCompleted();
                }
            };
        }

        /**
         * 双向流式调用
         * 这里可以演示,服务端并不一定要等待客户端消息完成再响应
         * 而是来一条响应一条,可以看到客户端以边在发送消息,一边在接受服务端的响应
         * 两条流是独立的
         *
         * @param responseObserver 请求流
         * @return 可以这么认为,客户端A、客户端B都可以到某个点位,并做个记录。如果下一个人来到此点位,则返回曾经到过此处的Note信息。如果从来没有,则不返回。
         */
        @Override
        public StreamObserver<RouteNote> routeChat(StreamObserver<RouteNote> responseObserver) {
            return new StreamObserver<RouteNote>() {
                @Override
                public void onNext(RouteNote note) {
                    List<RouteNote> notes = getOrCreateNotes(note.getLocation());
                    for (RouteNote prevNode : notes.toArray(new RouteNote[0])) {
                        responseObserver.onNext(prevNode);
                    }
                    notes.add(note);
                }

                @Override
                public void onError(Throwable throwable) {
                    log.error("routeChat  cancel", throwable);
                }

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


        //检查点位是否在“数据库”特征值中
        //如果存在,则返回由point + name组成的特征点位信息
        //否则,返回由point + name=“” 的特征点位信息
        private Feature checkFeature(Point location) {
            for (Feature feature : features) {
                if (Objects.equals(feature.getLocation().getLatitude(), location.getLatitude())
                        && Objects.equals(feature.getLocation().getLongitude(), location.getLongitude())) {
                    return feature;
                }
            }
            return Feature.newBuilder().setName("").setLocation(location).build();
        }

        private static int calcDistance(Point start, Point end) {
            int r = 6371000;
            double lat1 = Math.toRadians(RouteGuideUtil.getLatitude(start));
            double lat2 = Math.toRadians(RouteGuideUtil.getLatitude(end));
            double lon1 = Math.toRadians(RouteGuideUtil.getLongitude(start));
            double lon2 = Math.toRadians(RouteGuideUtil.getLongitude(end));
            double deltaLat = lat2 - lat1;
            double deltaLon = lon2 - lon1;
            double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2)
                    + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
            double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
            return (int) (r * c);
        }

        private List<RouteNote> getOrCreateNotes(Point location) {
            List<RouteNote> notes = Collections.synchronizedList(new ArrayList<>());
            List<RouteNote> prevNotes = routeNotes.putIfAbsent(location, notes);
            //如果此point之前存在,则返回之前所有到过此处的RouteNote信息
            return CollectionUtils.isEmpty(prevNotes) ? notes : prevNotes;
        }
    }

}

4-2 工具类

package top.joylee.example.grpc;

import top.joylee.example.grpc.route.Feature;
import top.joylee.example.grpc.route.FeatureDataBase;
import top.joylee.example.grpc.route.Point;
import com.google.protobuf.util.JsonFormat;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;

public final class RouteGuideUtil {
    private static final double COORD_FACTOR = 1e7;

    public static double getLatitude(Point location) {
        return location.getLatitude() / COORD_FACTOR;
    }

    public static double getLongitude(Point location) {
        return location.getLongitude() / COORD_FACTOR;
    }

    public static URL getDefaultFeaturesFile() {
        return RouteGuideServer.class.getResource("route_guide_db.json");
    }

    public static List<Feature> parseFeatures(URL file) throws IOException {
        try (InputStream input = file.openStream()) {
            try (Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8)) {
                FeatureDataBase.Builder database = FeatureDataBase.newBuilder();
                JsonFormat.parser().merge(reader, database);
                return database.getFeatureList();
            }
        }
    }

    public static boolean exists(Feature feature) {
        return Objects.nonNull(feature) && !feature.getName().isEmpty();
    }
}

 代码结构

4-3 客户端以及运行结果展示

4-3-1 客户端核心代码

package top.joylee.example.grpc;

import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.joylee.example.grpc.route.*;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class RouteGuideClient {
    private static final Logger log = LoggerFactory.getLogger(RouteGuideClient.class);
    private final Random random = new Random();
    // 阻塞stub
    private final RouteGuideGrpc.RouteGuideBlockingStub blockingStub;
    // 非阻塞stub
    private final RouteGuideGrpc.RouteGuideStub asyncStub;

    public RouteGuideClient(Channel channel) {
        blockingStub = RouteGuideGrpc.newBlockingStub(channel);
        asyncStub = RouteGuideGrpc.newStub(channel);
    }

    /**
     * 定义获取点位特征值的方法
     * 指定经度、纬度,拼装point message
     * 远程调用服务端,得到响应结果
     * 如果feature不为空而且feature中name的值不为空,则证明找到了
     * 否则,没找到该点位
     *
     * @param lat 经度
     * @param lon 纬度
     */
    public void getFeature(int lat, int lon) {
        log.info("***  Get Feature:[{},{}]", lat, lon);
        final Point request = Point.newBuilder()
                .setLatitude(lat)
                .setLongitude(lon)
                .build();

        //阻塞等待服务端响应
        final Feature feature = blockingStub.getFeature(request);
        if (Objects.nonNull(feature) && RouteGuideUtil.exists(feature)) {
            log.info("Found feature ,value = {}", feature);
        } else {
            log.info("Not found point,value = {}", request);
        }
    }

    /**
     * 通过指定“矩形”边界,来获取该边界内的特征点位
     * 瓶装 Rectangle message
     * 远程调用服务端,得到响应结果,并打印
     *
     * @param lowLat 低位经度
     * @param lowLon 低位纬度
     * @param hiLat  高位经度
     * @param hiLon  高位纬度
     */
    public void listFeatures(int lowLat, int lowLon, int hiLat, int hiLon) {
        log.info("*** ListFeatures: lowLat={} lowLon={} hiLat={} hiLon={}",
                lowLat, lowLon, hiLat, hiLon);

        final Rectangle rectangle = Rectangle.newBuilder()
                .setLo(Point.newBuilder().setLatitude(lowLat).setLongitude(lowLon).build())
                .setHi(Point.newBuilder().setLatitude(hiLat).setLongitude(hiLon).build())
                .build();
        //该调用与简单调用模式基本相似,只不过返回的是迭代对象。客户端可以用这个读取所有的返回对象。
        final Iterator<Feature> featureIterator = blockingStub.listFeatures(rectangle);
        for (int i = 1; featureIterator.hasNext(); i++) {
            final Feature feature = featureIterator.next();
            log.info("Feature {},value = {}", i, feature);
        }
    }

    /**
     * 异步、客户端流式调用
     * 将需要遍历的feature列表以流的方式发给服务端,服务端以流的方式接受请求消息,并处理消息。最后,处理完成所有消息后,将单个统计结果响应给客户端。
     * 这里我们需要用到异步stub
     */
    public void recordRoute(List<Feature> features, int numPoints) throws InterruptedException {
        log.info("*** RecordRoute ***");
        final CountDownLatch finishLatch = new CountDownLatch(1);

        //新建一个StreamObserver(用来接受响应信息,此处基本上与服务端保持一致,只不过服务端写,这里读)
        StreamObserver<RouteSummary> responseObserver = new StreamObserver<RouteSummary>() {
            @Override
            public void onNext(RouteSummary summary) {
                log.info("Finished trip with {} points. Passed {} features. "
                                + "Travelled {} meters. It took {} seconds.",
                        summary.getPointCount(),
                        summary.getFeatureCount(),
                        summary.getDistance(),
                        summary.getElapsedTime());
            }

            @Override
            public void onError(Throwable throwable) {
                log.error("RouteRecord Error", throwable);
                finishLatch.countDown();
            }

            @Override
            public void onCompleted() {
                log.info("Finished RecordRoute");
                //服务端响应完成后,放开门闩
                finishLatch.countDown();
            }
        };

        //新建一个StreamObserver作为客户端请求流,使用到异步stub,并指定该返回流(只不过在服务端,此流处理为只返回一条消息就completed)
        StreamObserver<Point> requestObserver = asyncStub.recordRoute(responseObserver);
        try {
            // 随机选择已存在的特征点位,作为列表发送给服务端
            for (int i = 0; i < numPoints; ++i) {
                int index = random.nextInt(features.size());
                Point point = features.get(index).getLocation();
                log.info("Visiting point {}, {}",
                        RouteGuideUtil.getLatitude(point),
                        RouteGuideUtil.getLongitude(point));
                // 将获取的点位放到请求流中
                requestObserver.onNext(point);
                // 让程序随机睡眠一小会,发送下一条记录。
                Thread.sleep(random.nextInt(1000) + 500);
                if (finishLatch.getCount() == 0) {
                    // 在发送请求的过程中,如果服务端抛异常,我看看到我们responseObserver中会接受到这个异常,并 finishLatch.countDown()
                    // 这个时候我们就要停止发送
                    return;
                }
            }
        } catch (RuntimeException e) {
            // Cancel RPC
            requestObserver.onError(e);
            throw e;
        }

        // 发送完所有点位后,结束请求流
        requestObserver.onCompleted();

        // Receiving happens asynchronously
        // 等待一分钟
        // 等待的是requestObserver发送完成后,服务端的处理时间(并不是等待request发送完)
        if (!finishLatch.await(1, TimeUnit.MINUTES)) {
            log.error("recordRoute can not finish within 1 minutes");
        }
    }

    /**
     * 双向流式调用
     * 每一次指定一个clientName,以作为记号,以便演示
     * 基本上跟客户端流式调用一样
     * 但是这里可以演示,服务端并不一定要等待客户端消息完成再响应
     * 而是来一条响应一条,可以看到客户端以边在发送消息,一边在接受服务端的响应
     * 两条流是独立的
     */
    public CountDownLatch routeChat(String clientName) {
        log.info("*** RouteChat ***");
        final CountDownLatch finishLatch = new CountDownLatch(1);
        StreamObserver<RouteNote> requestObserver =
                asyncStub.routeChat(new StreamObserver<RouteNote>() {
                    @Override
                    public void onNext(RouteNote note) {
                        log.info("{} Got message \"{}\" at {}, {}",
                                clientName,
                                note.getMessage(),
                                note.getLocation().getLatitude(),
                                note.getLocation().getLongitude());
                    }

                    @Override
                    public void onError(Throwable t) {
                        log.error("RouteChat Failed: {}", Status.fromThrowable(t));
                        finishLatch.countDown();
                    }

                    @Override
                    public void onCompleted() {
                        log.info("Finished RouteChat");
                        finishLatch.countDown();
                    }
                });

        try {
            RouteNote[] requests = {
                    newNote(clientName + " First message", 0, 0),
                    newNote(clientName + " Second message", 0, 10_000_000),
                    newNote(clientName + " Third message", 10_000_000, 0),
                    newNote(clientName + " Fourth message", 10_000_000, 10_000_000)};

            for (RouteNote request : requests) {
                log.info("Sending message \"{}\" at {}, {}",
                        request.getMessage(),
                        request.getLocation().getLatitude(),
                        request.getLocation().getLongitude());
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                requestObserver.onNext(request);
            }
        } catch (RuntimeException e) {
            // Cancel RPC
            requestObserver.onError(e);
            throw e;
        }
        // Mark the end of requests
        requestObserver.onCompleted();

        // return the latch while receiving happens asynchronously
        return finishLatch;
    }

    private RouteNote newNote(String message, int lat, int lon) {
        return RouteNote.newBuilder().setMessage(message)
                .setLocation(Point.newBuilder().setLatitude(lat).setLongitude(lon).build()).build();
    }

    public static void main(String[] args) throws InterruptedException {
        String target = "localhost:18080";
        List<Feature> features;
        try {
            features = RouteGuideUtil.parseFeatures(RouteGuideUtil.getDefaultFeaturesFile());
        } catch (IOException ex) {
            ex.printStackTrace();
            return;
        }
        ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
        try {
            RouteGuideClient client = new RouteGuideClient(channel);
            //TODO: 分别演示客户端调用
        } catch (Exception e) {
            log.error("gRPC ERROR", e);
        } finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }
}

4-3-2 简单RPC请求 演示

客户端请求代码:

public static void main(String[] args) throws InterruptedException {
        String target = "localhost:18080";
        List<Feature> features;
        try {
            features = RouteGuideUtil.parseFeatures(RouteGuideUtil.getDefaultFeaturesFile());
        } catch (IOException ex) {
            ex.printStackTrace();
            return;
        }
        ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
        try {
            RouteGuideClient client = new RouteGuideClient(channel);
            //特征点位存在
            client.getFeature(409146138, -746188906);
            //特征点位不存在
            client.getFeature(0, 0);
        } catch (Exception e) {
            log.error("gRPC ERROR", e);
        } finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }

运行结果:

 4-3-3 服务端流式调用

客户端请求示例代码:

public static void main(String[] args) throws InterruptedException {
        String target = "localhost:18080";
        List<Feature> features;
        try {
            features = RouteGuideUtil.parseFeatures(RouteGuideUtil.getDefaultFeaturesFile());
        } catch (IOException ex) {
            ex.printStackTrace();
            return;
        }
        ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
        try {
            RouteGuideClient client = new RouteGuideClient(channel);
            // Looking for features between 40, -75 and 42, -73.
            client.listFeatures(400000000, -750000000, 420000000, -730000000);
        } catch (Exception e) {
            log.error("gRPC ERROR", e);
        } finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }

运行结果:

 4-3-4 客户端流式请求

客户端请求示例代码:

 public static void main(String[] args) throws InterruptedException {
        String target = "localhost:18080";
        List<Feature> features;
        try {
            features = RouteGuideUtil.parseFeatures(RouteGuideUtil.getDefaultFeaturesFile());
        } catch (IOException ex) {
            ex.printStackTrace();
            return;
        }
        ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
        try {
            RouteGuideClient client = new RouteGuideClient(channel);
            // Record a few randomly selected points from the features file.
            client.recordRoute(features, 10);
        } catch (Exception e) {
            log.error("gRPC ERROR", e);
        } finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }

运行结果:

4-3-5 双向流式调用

客户端请求示例代码:

public static void main(String[] args) throws InterruptedException {
        String target = "localhost:18080";
        List<Feature> features;
        try {
            features = RouteGuideUtil.parseFeatures(RouteGuideUtil.getDefaultFeaturesFile());
        } catch (IOException ex) {
            ex.printStackTrace();
            return;
        }
        ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
        try {
            RouteGuideClient client = new RouteGuideClient(channel);
            // Send and receive some notes.
            // 三次请求,模拟3个客户端到同一个地点,看服务端的响应结果
            CountDownLatch finishLatchA = client.routeChat("ClientA");
            if (!finishLatchA.await(1, TimeUnit.MINUTES)) {
                log.error("routeChat can not finish within 1 minutes");
            }
            final CountDownLatch finishLatchB = client.routeChat("ClientB");
            if (!finishLatchB.await(1, TimeUnit.MINUTES)) {
                log.error("routeChat can not finish within 1 minutes");
            }
            final CountDownLatch finishLatchC = client.routeChat("ClientB");
            if (!finishLatchC.await(1, TimeUnit.MINUTES)) {
                log.error("routeChat can not finish within 1 minutes");
            }
        } catch (Exception e) {
            log.error("gRPC ERROR", e);
        } finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }

 运行结果:

;