概览
在gRPC的官方文档中这样描述grpc的特点:
第一点:强大的接口描述语言(Powerful IDL)
Protocol Buffers是一个强大的二进制序列化工具集和语言,你可以使用Protocol Buffers定义你的接口。
第二点:支持十种语言的类库
为各种语言编写的服务自动生成相应语言的客户端和服务端存根(也就是接口)
第三点:基于HTTP2协议
基于HTTP2标准设计,带了许多诸如双向流、流程控制、头部压缩、单TCP连接上的多路复用请求等特性。
这些特性使得其在移动设备上表现更好,更省电和节省空间占用,同时加速了运行在cloud上的服务和web应用。
那么什么是gRPC呢?
gRPC客户端可以直接调用另外一台机器上服务器应用程序的方法,就像调用本地对象一样,这使得创建分布式的应用和服务更加方便。
和众多RPC系统一样,gPRC的基本思想是:定义一个接口服务,指定服务中可以通过参数和返回值类型进行远程调用的方法。在服务端,实现这个接口,运行一个gRPC服务器来处理客户端调用请求。在客户端,保留了一个存根(指的是某种语言编写的客户端)提供了和服务端一样的方法。
进行通信的gRPC客户端和服务端可以运行在不同的环境上—从Google的内置服务到个人桌面应用—可以是任何gRPC支持的语言。所以,举例来说,你可以简单创建一个使用Java编写的gRPC服务,然后使用Go或者Python,或者Ruby编写的客户端去访问。另外,最新的Google APIs将会有gRPC版本,可以使你很方便地在你的应用中使用Google 的功能。
使用Protocol buffers
gRPC默认使用protocol buffers—Google 的成熟开源机制,用来序列化结构化数据(即便如此,它还可以和其他数据结构例如JSON一起使用)。如你所见,我们下边的例子,将使用proto文件定义gRPC服务,其方法参数和返回类型作为protocol buffer的消息类型。
Protocol buffer 版本
虽然protocol buffers已经在开源用户中使用有一段时间了,但是我们给的例子中使用的却是一个新版本的protocol buffers,叫做proto3。proto3有着轻量级的简化语法,一些有用的新特性,支持更多的语言。当前发布的beta版有Java、C++、Python、Objective-C和C#。alpha版本有protocol buffers Github repo上的JavaNano(Android Java),Ruby,C++和JavaScript,还有golang/protobuf Github repo上的Go语言生成器,更多语言正在开发中。你会在proto3语言指南和各种语言的参考文档中找到更多信息,在release note中看到当前版本的主要改进。
一般情况下,当你还停留在proto2的时候(当前默认protocol buffers版本),我们推荐你使用proto3和gRPC一起使用,因为它能让你使用gRPC支持的全部语言,而且能避免proto2客户端与proto3服务端或者proto3客户端与proto2服务端通信时带来的兼容性问题。
Hello gRPC!
既然你已经了解了gRPC,那么接下来,我们需要搞清它的工作原理,最简单的办法是看一个简单的例子。我们的Hello World将想你展示一个简单的gRPC客户端-服务器应用的构成,并教你如何下手:
1.创建一个protocol buffers schema,定一个简单的RPC服务,它只有一个HelloWorld方法。
2.创建一个服务器,用你喜欢的语言实现上边的接口。
3.用你最喜欢的语言编写一个客户端访问你的服务器。
这个例子的完整代码在我们Github仓库的examples目录下。我们使用Git版本控制系统进行代码管理:然而,你不需要知道任何关于Git的知识,除了知道如何安装和执行一些个git命令。
注:我们的服务端代码的例子不是适用于所有语言,比如gRPC PHP和Object-C只是用来做客户端。
这只是一个介绍性的例子,而不是针对某个特定语言的综合教程。你可以在本站找到更多的深入案例和参考文
设置
这个部分介绍如何在你本机上进行设置来运行我们的例子。如果你只是想阅读源代码,你可以直接跳过这个步骤
安装Git
略~
使用Java作为首选语言
1. 获取源码
git clone https://github.com/grpc/grpc-java.git --克隆java源码
cd grpc-java/examples --切换目录到examples
2. 安装gRPC
为了能够运行例子(和我们的教程示例,以及你创建的任何的gRPC项目),你需要为你选择的语言安装gRPC运行时。另外,如果你想要尝试生成gRPC代码,根据你选择的语言,可能需要安装protocol buffers编译器和相应的gRPC插件。未来的版本中我们会进一步简化这个过程。
注:这个例子也是Java gRPC自身构建的一部分,
所以,运行它还是和运行一个常规的项目不太一样的。安装和构建请按照Quickstart中的指示。
添加下面的内容到你的build文件中,以安装你项目所需的运行时jar依赖:
Gradle:—使用Gradle作为构建工具
compile ‘io.grpc:grpc-all:0.15.0‘
Maven:—使用Maven作为构建工具,将如下添加到examples目录下的pom.xml文件的dependencies模块中
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>0.15.0</version>
</dependency>
3. 定义一个服务
创建我们例子的第一步是定义一个服务:一个PRC服务指定了可以通过方法参数和返回值类型进行远程调用的方法。如你在overview篇章中所见,gRPC使用protocol buffers。我们使用protocol buffer接口定义语言(IDL)定义我们的服务方法,其参数和返回值都为protocol buffer消息类型。客户端和服务器端都会使用由服务定义产生的接口代码。
在我们的服务定义文件——helleworld.proto中使用了protocol buffers IDL。Greeter服务有一个方法SayHello指出:服务端接受一个来自远程客户端的HelloRequest消息,这个消息中包含了用户的姓名,然后返回一个打招呼的HelloReply。这是你可以在gRPC中定义的最简单的RPC——你可以在你所选择的语言的教程中找到更多类型。
syntax = "proto3";
option java_package = "io.grpc.examples";
package helloworld;
// The greeter service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user‘s name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
如你所见,一个gRPC方法只接受一个protocol buffer消息类型作为它的请求,并只返回一个protocol buffer类型作为它的响应—这也是所有的gRPC方法的使用场景。然而,它不是限制你以何种样的数据在gRPC客户端和服务器之间进行传递—想要添加更多的参数或者返回值,你只需要向protocol buffer请求和响应类型中添加适当的字段即可。
生成gRPC代码
一旦我们定义好了我们的服务,我们使用protocol buffer编译器(protoc )来生成创建我们应用所需的特定的客户端和服务器端代码—你可以以任何gRPC支持的语言生成gRPC代码,即便PHP和Object-C仅支持创建客户端。生成的代码包含了客户端用来调用的代码和一个需要实现的服务端的抽象接口,这些方法都定义在我们的Greeter 服务器中。
正如在安装部分提到的,这个例子的构建系统是Java gRPC自身构建的一部分——简单起见,我们推荐你使用我们为这个例子预生成的代码。你可以参考README文件,了解如何由你的.proto文件生成代码。
我在另一篇文章中讲述如何使用maven插件来由.proto文件自动生成gRPC的基础代码:gRPC动手实践。
这个例子的预生成代码在src/generated/main目录下。下面的类包含了我们为这个例子预生成的所有代码:
- HelloRequest.java, HelloResponse.java, and others which have all the
protocol buffer code to populate, serialize, and retrieve our
HelloRequest and HelloReply message types
- GreeterGrpc.java, 包含了 :
2.1 一个抽象基类GreeterImplBase,由Greeter服务器端去实现
public static abstract class GreeterImplBase {
...
public void sayHello(Helloworld.HelloRequest request,
StreamObserver<Helloworld.HelloReply> responseObserver) {
...
}
}
2.2 一个存根类GreeterStub,客户端使用它和Greeter服务进行通信:
public static class GreeterStub extends AbstractStub<GreeterStub> {
...
}
编写服务端
现在让我们写些代码吧!首先我们将创建一个服务端应用实现我们的服务(你可还记得,我们可以使用除了Object-C和PHP外的所有语言)。在这一部分中我们不会对如何创建一个服务端进行深入的讲解——更多信息,会在你选择的语言的教程中。
服务实现
GreeterImpl.java 实现了我们的 Greeter 规定的行为。
如你所见,类 GreeterImpl 继承自通过IDL生成的抽象类 GreeterGrpc.GreeterImplBase ,并实现了sayHello方法。
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
sayHello 有两个参数:
HelloRequest: 请求
StreamObserver:一个响应观察者,这是一个特殊的接口 ,被服务器的响应对象调用
为了向客户端返回我们的响应,完成调用:
上边的代码中,我们使用接口定义中指定的消息构造出了一个HelloReply响应对象。
我们返回HelloReply给客户端,然后指明我们已经完成了RPC的处理。
Server实现
另外一个提供gRPC服务的主要的特性是,要使我们实现的服务能够通过网络被访问到。
HelloWorldServer.java为我们的Java例子提供了如下:
/* The port on which the server should run */
private int port = 50051;
private Server server;
private void start() throws Exception {
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 has been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldServer.this.stop();
System.err.println("*** server shut down");
}
});
}
这里我们创建了一个合适的gRPC服务器,绑定了Greeter服务实现到我们创建的端口上。然后我们开始启动服务:服务器准备接收来在Greeter服务客户端在我们指定端口上的请求。我们会在语言-说明文档中更详细介绍这是如何工作的。
编写客户端
客户端gRPC很简单。在这一步骤,我们将使用生成的代码来编写一个简单的客户端,来访问在上一部分中创建的Greeter服务器。
连接服务
首先,我们来看如何连接到Greeter 服务器。首先我们需要创建一个gRPC通道,指明主机名hostname和我们想连接的服务器的端口号。然后我们使用通道构建客户端实例。
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
public HelloWorldClient(String host, int port) {
channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext(true)
.build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
在这个例子中,我们创建了一个阻塞的存根。这就意味着RPC调用等待一直等待着服务器响应,它会返回一个响应体或者抛出一个异常。gRPC Java也有其他一些存根可以构造非阻塞的调用,响应是异步的返回地。
调用RPC
现在我们可以连接服务获取一个“问候”:
我们构造和填充和一个HelloRequest发送给服务。
我们用我们的请求调用客户端存根的SayHello() RPC,如果RPC成功,得到一个封装过的HelloReply,从中我们可以获取到我们的“问候”。
HelloRequest req = HelloRequest.newBuilder().setName(name).build();
HelloReply reply = blockingStub.sayHello(req);
你可以在 HelloWorldClient.java中看到完整的代码。
试一下吧!
你可以尝试构建和运行我们给的使用同一种语言编写客户端和服务端的例子。或者你也可以试着gRPC最有用的特性—不同语言代码之间的互操作性—运行一个使用不同语言编写的服务端和客户端。每个服务和客户端都使用了由同一个proto生成的接口代码,也就说,任何Greeter客户端可以同任意的Greeter服务器进行通信。
首先,你需要切换到examples目录下,找到你的构建配置文件gradle.properties,修改gen-grpc-java的版本号(默认与上边的dependencies一致,为${grpc-Version}):
然后运行以下代码,构建客户端和服务器
$ ./gradlew -PskipCodegen=true installDist
然后运行服务器,它会监听在 50051端口上:
$ ./build/install/grpc-examples/bin/hello-world-server
一旦服务器运行起来之后,在另外一个命令窗口中运行客户端确认它能收到消息:
$ ./build/install/grpc-examples/bin/hello-world-client