#之前在BAT某厂实习用的就是公司内部自研的 类gRPC 开发框架,当时就觉得这个高性能和轻量级、可跨语言项目泛用,并且可以方便自定义服务接口与开发的框架类型非常有趣,在一边读框架源码一边开发过程中也受益匪浅。
所以之后专门在gRPC官网跟踪学习与代码分享,现在分享一下关于后续 在官网学习中 Google的gRPC框架基本开发使用指南
gRPC概念
在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,可以更容易地创建分布式应用和服务。
gRPC 基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
gRPC 客户端和服务端可以在多种环境或者不同语言的项目运行和交互。只要在.proto文件用protoBuf统一接口服务类型(包括传入与返回参数类型与格式)后,不同语言项目可以畅快通信,比如下图用 Java 创建一个 gRPC 服务端,用 Go、Python、Ruby 来创建客户端。
如何使用gRPC的Demo

一、下载安装git
二、拉取gRPC源码
Java
git clone https://github.com/grpc/grpc-java.git cd grpc-java/examples //切换当前目录到grpc-java/examples
GO
$ go get -u github.com/grpc/grpc-go/examples/helloworld/greeter_client $ go get -u github.com/grpc/grpc-go/examples/helloworld/greeter_server //再切换当前目录到 examples/helloworld
Python
$ git clone https://github.com/grpc/grpc.git $ cd examples/python/helloworld/ //切换当前目录到 examples/python/helloworld
三、protobuf定义服务类型
(1).proto文件里 定义接口服务与请求返回参数
使用 protocol buffers 接口定义语言来定义服务方法,用 protocol buffer 来定义参数和返回类型。客户端和服务端均使用服务定义生成的接口代码。
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;
}
(2)用protoc编译器生成代码
定义好.proto文件的服务后,我们可以使用 protocol buffer 编译器 protoc 来生成创建应用所需的特定客户端和服务端的代码 - 你可以生成任意 gRPC 支持的对应项目语言的代码
Java编译生成代码
//.proto文件中定义的消息类型会生成HelloRequest.java和HelloResponse.java
//并且.proto文件中定义的服务会生成GreeterGrpc.java。它包含了用于实现服务的所有代码。例如,它包含了一个接口(Greeter),这个接口定义了服务中的所有方法。
//这样优点是protobuf生成的代码可以处理所有的序列化和反序列化,使得我们可以专注于实现服务的业务逻辑,而不需要关心数据的传输和存储
GreeterGrpc.java 包含如下 (不限其他代码)
//Greeter 服务端需要实现的接口
public static interface Greeter {
public void sayHello(Helloworld.HelloRequest request,
StreamObserver<Helloworld.HelloReply> responseObserver);
}
//客户端用来与 Greeter 服务端进行对话的 存根 类。就像你所看到的,异步存根也实现了 Greeter 接口
public static class GreeterStub extends AbstractStub<GreeterStub>
implements Greeter {
...
}
GO编译器生成代码
protoc -I ../protos ../protos/helloworld.proto --go_out=plugins=grpc:helloworld //运行 protocol buffer 编译器 //生成了 helloworld.pb.go ,包含客户端和服务端类,此外还有用于填充、序列化、提取 HelloRequest 和 HelloResponse 消息类型的类
Python编译器生成代码
$ ./run_codegen.sh //生成客户端和服务端 $ protoc -I ../../protos --python_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_python_plugin` ../../protos/helloworld.proto //调用 protocol buffer编译器,生成helloworld_pb2.py,包含我们生成的客户端和服务端类
四、服务器端
(1)服务功能实现&&服务端实现
Java服务实现
GreeterImpl.java 实现 Greeter 服务所需要的行为。它通过实现 sayHello 方法,实现了从 IDL 生成的GreeterGrpc.Greeter 接口
@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 应答对象。将 HelloReply 返回给客户端,就完成了对 RPC 的处理。*/
Java服务端实现
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(GreeterGrpc.bindService(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");
}
});
}
GO服务实现
greeter_server/main.go 实现了 Greeter 服务所需要的行为。
服务器有一个 server 结构。它通过实现 sayHello 方法,实现了从 proto 服务定义生成的GreeterServer 接口:
// server is used to implement helloworld.GreeterServer.
type server struct{}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
/*为了返回给客户端应答并且完成调用:
用消息构建并填充一个在我们接口定义的 HelloReply 应答对象。
将 HelloReply 返回给客户端。*/
GO服务端实现
greeter_server/main.go 提供了以下代码作为 Go 的例子
const (
port = ":50051"
)
...
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
s.Serve(lis)
}
Python服务实现
greeter_server.py 实现了 Greeter 服务所需要的行为。 它通过实现 sayHello 方法,实现了从 proto 服务定义生成的helloworld_pb2.BetaGreeterServicer 接口:
class Greeter(helloworld_pb2.BetaGreeterServicer): def SayHello(self, request, context): return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name) /* 为了返回给客户端应答并且完成调用: 1. 用消息构建并填充一个在我们接口定义的 `HelloReply` 应答对象。 2. 将 `HelloReply` 返回给客户端。*/
Python服务端实现
greeter_server.py 提供了以下代码作为 Python 的例子。
server = helloworld_pb2.beta_create_Greeter_server(Greeter())
server.add_insecure_port('[::]:50051')
server.start()
try:
while True:
time.sleep(_ONE_DAY_IN_SECONDS)
except KeyboardInterrupt:
server.stop()
五、客户端
客户端的 gRPC 结构简单。用生成的代码写一个简单的客户程序来访问在上一节里创建的 Greeter 服务器。
连接 Greeter 服务器:先创建一个 gRPC 频道,指定要连接的主机名和服务器端口。然后用这个频道创建存根实例。
(1)连接服务
Java连接服务
创建了一个阻塞的存根。这意味着 RPC 调用要等待服务器应答,将会返回一个应答或抛出一个异常。 gRPC Java 还可以有其他种类的存根,可以向服务器发出非阻塞的调用,这种情况下应答是异步返回的。
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);
}
GO连接服务
在 gRPC Go 你是使用一个特殊的 Dial() 方法来创建频道
const (
address = "localhost:50051"
defaultName = "world"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
...
}
Python连接服务
生成的 Python 代码有一个根据频道创建存根的帮助方法。
channel = implementations.insecure_channel('localhost', 50051)
stub = helloworld_pb2.beta_create_Greeter_stub(channel)
...
(2)调用RPC
现在可以联系服务并获得一个 greeting :
-
我们创建并填充一个
HelloRequest发送给服务。 -
我们用请求调用存根的
SayHello(),如果 RPC 成功,会得到一个填充的HelloReply,从其中我们可以获得 greeting。
Java
可以在 HelloWorldClient.java 里查看完整的客户端代码
HelloRequest req = HelloRequest.newBuilder().setName(name).build(); HelloReply reply = blockingStub.sayHello(req);
GO
可以在 greeter_client/main.go 里查看完整的客户端代码
r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.Message)
Python
可以在 greeter_client.py 里查看完整的客户端代码
response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'), _TIMEOUT_SECONDS) print "Greeter client received: " + response.message
788

被折叠的 条评论
为什么被折叠?



