gRPC

本文介绍了Google的gRPC框架,它基于HTTP/2和Protocol Buffers进行数据传输。首先,文章讲解了Protocol Buffers的基础,包括如何定义消息和服务,并阐述了其与XML的区别。接着,通过一个实例展示了gRPC的使用流程,包括.proto文件的编译、服务端和客户端程序的编写及运行。最后,提到了gRPC的四种通信方式,并预告了未来将深入探讨gRPC的源码分析。

gRPC是Google开源的一个RPC框架,基于HTTP2.0传输数据,数据序列化框架为Google开源的Protocol Buffers

一、先简单介绍下Protocol Buffers

同样作为数据序列化框架,Protocol Buffers比XML更小、更快、更易于编程,它将消息和服务定义在.proto文件中,语言无关,在Java中用它自己的编译器将.proto文件直接编译成Java类

1.定义消息
syntax = "proto3";

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3;
}

指定元素类型:可以指定元素类型为基本类型,也可以指定为enum或者其他message

指定元素代号:在二进制格式的消息中标识元素,是从1开始的数字

指定元素约束:默认情况下元素可以出现0次或1次;repeated:元素可以出现任意次

添加注释:与Java注释相同,//或者/* ……*/

添加reserved元素:弃用某些元素名或元素代号

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

元素基本类型和Java类型的对应关系:double->double, float->float, int32->int, int64->long, bool->boolean, string->String, bytes->BytesString

枚举类型:

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3 [default = 10];
  enum Corpus {
    //option allow_alias = true;
    //OTHER = 6;    allow_alias为true可以指定不同枚举元素为相同的值
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

注意枚举类型的元素代码从0开始,枚举元素也可以弃用某些元素名或元素代码

enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

默认值:string->empty string, bytes->empty bytes, numeric->0, bool->false, enum->第一个枚举元素

导入其他消息定义:使用public关键字可以传递导入

// old.proto
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

消息可以嵌套定义

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}
2.定义服务
service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

二、从实例出发了解gRPC

gRPC的使用流程是这样的:
1.在.proto文件中定义消息体和服务,规定客户端要发送的消息和服务器的通信方式
2.编译.proto文件,生成Java类
3.编写Server和Client端程序,定义通信方法
4.运行服务器程序,监听请求
5.运行客户端程序,发送请求消息,接收返回消息

Protocol Buffers和gRPC都需要自己的编译器,先添加maven依赖

<properties>
    <grpc.version>1.13.1</grpc.version>
    <protobuf.version>3.5.1</protobuf.version>
    <protoc.version>3.5.1-1</protoc.version>
</properties>

<dependencies>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-netty</artifactId>
        <version>${grpc.version}</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>${grpc.version}</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>${grpc.version}</version>
    </dependency>
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java-util</artifactId>
        <version>${protobuf.version}</version>
    </dependency>
</dependencies>

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

参考gRPC官网,了解到gRPC有四种通信方式
1.客户端发单个消息,服务器发单个消息
2.客户端发单个消息,服务器发消息流
3.客户端发消息流,服务器发单个消息
4.客户端发消息流,服务器发消息流
于是我们在.proto文件中定义四个方法,模拟这四种通信方式

syntax = "proto3";

option java_package = "learn_grpc";

service Service {
    rpc SayHello (SayHelloRequest) returns (SayHelloReply) {
    }
    rpc GetServerPeopleList (GetServerPeopleListRequest) returns (stream
    GetServerPeopleListReply) {
    }
    rpc CountClientPeople (stream CountClientPeopleRequest) returns (CountClientPeopleReply) {
    }
    rpc Chat (stream ChatContent) returns (stream ChatContent) {
    }
}

message SayHelloRequest {
    string name = 1;
    string helloWords = 2;
}

message SayHelloReply {
    string reply = 1;
}

message GetServerPeopleListRequest {

}

message GetServerPeopleListReply {
    string name = 1;
}

message CountClientPeopleRequest {

}

message CountClientPeopleReply {
    int32 count = 1;
}

message ChatContent {
    string content = 1;
}

编译.proto文件,生成了对应的ServiceGrpc.class文件
新建Server和Client文件

public class ServiceServer {

    private final Server server;
    private final int port;

    //初始化server,指定端口号和服务
    public ServiceServer(int port) {
        this.port = port;
        server = ServerBuilder.forPort(port).addService(new Service()).build();
    }

    public void start() throws IOException, InterruptedException {
        server.start();
        System.out.println("server started");
        blockUntilShutdown();
    }

    public void stop() {
        server.shutdown();
        System.out.println("server stopped");
    }

    //服务器启动后进入等待状态
    public void blockUntilShutdown() throws InterruptedException {
        System.out.println("server blocked");
        server.awaitTermination();
    }

    //定义服务,实现方法
    static class Service extends ServiceGrpc.ServiceImplBase {

        //单个消息-单个消息
        @Override
        public void sayHello(LearnGrpc.SayHelloRequest request,
                StreamObserver<LearnGrpc.SayHelloReply> responseObserver) {
            String name = request.getName();
            //onNext方法里填写要返回的消息
            responseObserver.onNext(LearnGrpc.SayHelloReply.newBuilder()
                    .setReply("Welcome, " + name + "!").build());
            responseObserver.onCompleted();
        }

        //单个消息-消息流
        @Override
        public void getServerPeopleList(LearnGrpc.GetServerPeopleListRequest request,
                StreamObserver<LearnGrpc.GetServerPeopleListReply> responseObserver) {
            //多次执行onNext方法,返回消息流
            responseObserver
                    .onNext(LearnGrpc.GetServerPeopleListReply.newBuilder().setName("彭于晏").build());
            responseObserver
                    .onNext(LearnGrpc.GetServerPeopleListReply.newBuilder().setName("张震").build());
            responseObserver
                    .onNext(LearnGrpc.GetServerPeopleListReply.newBuilder().setName("靳东").build());
            responseObserver.onCompleted();
        }

        //消息流-单个消息
        @Override
        public StreamObserver<LearnGrpc.CountClientPeopleRequest> countClientPeople(
                final StreamObserver<LearnGrpc.CountClientPeopleReply> responseObserver) {
            //初始化请求流
            return new StreamObserver<LearnGrpc.CountClientPeopleRequest>() {

                int count = 0;

                //编写对请求流中每个请求的操作
                public void onNext(LearnGrpc.CountClientPeopleRequest countClientPeopleRequest) {
                    count++;
                }

                public void onError(Throwable throwable) {

                }

                //请求流发送完成
                public void onCompleted() {
                    //onNext方法里填写要返回的消息
                    responseObserver.onNext(
                            LearnGrpc.CountClientPeopleReply.newBuilder().setCount(count).build());
                    responseObserver.onCompleted();
                }
            };
        }

        //消息流-消息流
        @Override
        public StreamObserver<LearnGrpc.ChatContent>
                chat(final StreamObserver<LearnGrpc.ChatContent> responseObserver) {
            return new StreamObserver<LearnGrpc.ChatContent>() {
                //每接受一个请求就返回一个响应
                public void onNext(LearnGrpc.ChatContent chatContent) {
                    System.out.println(chatContent.getContent());
                    responseObserver.onNext(LearnGrpc.ChatContent.newBuilder()
                            .setContent("Hi, I'm server!").build());
                }

                public void onError(Throwable throwable) {

                }

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

    public static void main(String[] args) throws IOException, InterruptedException {
        ServiceServer serviceServer = new ServiceServer(50001);
        serviceServer.start();
    }
}
public class ServiceClient {

    private final ManagedChannel channel;
    private final ServiceGrpc.ServiceBlockingStub blockingStub;
    private final ServiceGrpc.ServiceStub asynStub;

    //用host和端口号构造通道,初始化阻塞stub和异步stub,分别用于发送单个消息和消息流
    public ServiceClient(String host, int port) {
        channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
        blockingStub = ServiceGrpc.newBlockingStub(channel);
        asynStub = ServiceGrpc.newStub(channel);
    }

    public void sayHello() {
        System.out.println("Method sayHello starts");
        LearnGrpc.SayHelloRequest request = LearnGrpc.SayHelloRequest.newBuilder().setName("client")
                .setHelloWords("hello").build();
        //直接接收单个消息返回值
        LearnGrpc.SayHelloReply reply = blockingStub.sayHello(request);
        System.out.println("Reply: " + reply.getReply());
        System.out.println("Method sayHello ends");
    }

    public void getServerPeopleList() {
        System.out.println("Method getServerPeopleList starts");
        System.out.println("People list:");
        LearnGrpc.GetServerPeopleListRequest request = LearnGrpc.GetServerPeopleListRequest
                .newBuilder().build();
        //用迭代器接收消息流
        Iterator<LearnGrpc.GetServerPeopleListReply> list = blockingStub
                .getServerPeopleList(request);
        while (list.hasNext()) {
            LearnGrpc.GetServerPeopleListReply reply = list.next();
            System.out.println(reply.getName());
        }
        System.out.println("Method getServerPeopleList ends");
    }

    public void countClientPeople() throws InterruptedException {
        System.out.println("Method countClientPeople starts");
        final CountDownLatch finishLatch = new CountDownLatch(1);
        LearnGrpc.CountClientPeopleRequest request = LearnGrpc.CountClientPeopleRequest.newBuilder()
                .build();
        StreamObserver<LearnGrpc.CountClientPeopleReply> responseObserver = new StreamObserver<LearnGrpc.CountClientPeopleReply>() {

            //onNext接收返回的单个消息
            public void onNext(LearnGrpc.CountClientPeopleReply countClientPeopleReply) {
                System.out.println("Client people count:");
                System.out.println(countClientPeopleReply.getCount());
            }

            public void onError(Throwable throwable) {

            }

            //接收完毕后打开障栅,结束方法
            public void onCompleted() {
                System.out.println("Method countClientPeople ends");
                finishLatch.countDown();
            }
        };
        StreamObserver<LearnGrpc.CountClientPeopleRequest> requestObserver = asynStub
                .countClientPeople(responseObserver);
        //多次发送消息,构成消息流
        requestObserver.onNext(request);
        requestObserver.onNext(request);
        requestObserver.onNext(request);
        requestObserver.onCompleted();
        //发送结束后利用障栅进行阻塞,等待服务器返回
        finishLatch.await();
    }

    public void chat() throws InterruptedException {
        System.out.println("Method chat starts");
        final CountDownLatch finishLatch = new CountDownLatch(1);
        LearnGrpc.ChatContent request1 = LearnGrpc.ChatContent.newBuilder()
                .setContent("Hello, I'm client!").build();
        LearnGrpc.ChatContent request2 = LearnGrpc.ChatContent.newBuilder()
                .setContent("Hello, I'm client!").build();
        StreamObserver<LearnGrpc.ChatContent> requestObserver = asynStub.chat(new StreamObserver<LearnGrpc.ChatContent>() {
            //接收到一个返回消息就调用一次onNext方法,用来接收消息流
            public void onNext(LearnGrpc.ChatContent chatContent) {
                System.out.println(chatContent.getContent());
            }

            public void onError(Throwable throwable) {

            }

            public void onCompleted() {
                System.out.println("Method chat ends");
                finishLatch.countDown();
            }
        });
        //多次调用onNext方法,发送消息流
        requestObserver.onNext(request1);
        requestObserver.onNext(request2);
        requestObserver.onCompleted();
        //发送结束后利用障栅进行阻塞,等待服务器返回
        finishLatch.await();
    }

    public static void main(String[] args) throws InterruptedException {
        ServiceClient serviceClient = new ServiceClient("localhost", 50001);
        serviceClient.sayHello();
        serviceClient.getServerPeopleList();
        serviceClient.countClientPeople();
        serviceClient.chat();
    }
}

先运行服务器,得到以下输出:

server started
server blocked

再运行客户端的四个方法,得到以下输出:

Method sayHello starts
Reply: Welcome, client!
Method sayHello ends
Method getServerPeopleList starts
People list:
彭于晏
张震
靳东
Method getServerPeopleList ends
Method countClientPeople starts
Client people count:
3
Method countClientPeople ends
Method chat starts
Hi, I'm server!
Hi, I'm server!
Method chat ends

通过以上实例,可以了解gRPC的基本使用方法
后续将会对gRPC再进行源码层面的剖析

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值