小菜鸡带你gRPC入门[Netty系列]

gRPC介绍及实际应用 介绍 在之前我已经学习过protobuf。而gRPC可以将protobuf用作其接口定义语言(IDL)和其基础消息交换格式。
我们来看官网给出的一张图 可以看出什么?
小菜鸡带你gRPC入门[Netty系列]
文章图片

至少我们可以看出grpc支持多种语言之间的通信吧。服务器端和客户端可以使用不同语言进行定义。
官方 在gRPC中,客户端应用程序可以直接在其计算机上的服务器应用程序上调用方法,就好像它是本地对象,这使得你可以更加轻松的创建分布式应用程序和服务。与许多RPC框架一样,gRPC围绕定义服务的思想,指定可通过其参数和返回类型远程调用的方法。【大部分RPC系统都是如此,比如说Apache的Thrift,大同小异】
核心概念 gRPC提供protobuf编译器插件 protobuf-gradle-plugin 下面有详细使用,这些插件可以为你生成客户端和服务区端代码。

  • 在服务器端,服务器实现服务声明的方法,并运行服务器来处理客户端调用。服务器端对传入请求进行解码,执行服务方法,再对服务响应进行编码。
  • 在客户端,客户端实现与服务相同的方法,将调用的参数包装在适当的protobuf消息类型中,gRPC将请求发送到服务器并获取响应。
gRPC一共有四种服务方法。
  1. Unary RPC 一元RPC:客户端向服务器发送单个请求并获取单个响应,就和普通函数调用一样。
    rpc SayHello(HelloRequest) returns (HelloResponse);

  2. Server streaming RPC 服务器流式RPC:客户端向服务器发送请求,并使用流进行消息读取。gPRC保证单个RPC调用中的消息顺序。
    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);

  3. Client streaming RPC 客户端流式RPC :客户端编写消息序列,将他们以流的形式发送到服务器端。等待服务器读取消息并响应。同样gRPC保证消息顺序。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);

  1. Bidirectional streaming RPC 双向流式PRC:双方都使用流进行读写和发送消息。这两个流是独立运行的,双方都可以按自己喜欢的顺序进行读写。每个流中的消息顺序都会保留。
    rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);

gRPC错误处理
gRPC如何处理错误以及gRPC错误代码。
gRPC调用成功时,服务器会想客户端返回状态。但是如果通信不成功呢?
如果发生错误,gRPC会返回其错误状态代码,并且错误消息是可选字符串,改消息提供有关事假你的详情信息。
错误状态码:我这里只列举几个。我还不想学它的错误处理啦。 在服务器上找不到方法:GRPC_STATUS_UNIMPLEMENTED 服务器关闭:GRPC_STATUS_UNAVAILABLE 解析返回状态时出错:GRPC_STATUS_UNKNOWN 错误解析响应protobuf:GRPC_STATUS_INTERNAL 错误解析请求protobuf:GRPC_STATUS_INTERNAL

grpc带有三种传输实现:
  1. 基于Netty的传输是基于Netty的主要传输实现 。它适用于客户端和服务器。
  2. 基于OkHttp的传输是基于OkHttp的轻量级传输 。它主要用于Android,并且仅用于客户端。
  3. 进程内传输适用于服务器与客户端处于同一进程中的情况。它对于测试很有用,同时也可以安全地用于生产。
编写.proto文件及编译生成java文件 注意gRPC使用的protobuf版本是proto3。官网的实例也都是基于proto3的。
使用gRPC我们可以在.proto文件中一次定义我们的服务【和thrift相同thrift也是在.thrift文件中定义服务】
并实现客户端和服务器端。
要使用protobuf编译器生成代码,我们需要使用相关插件,由于我是基于Gradle构建的系统所以使用的protobuf-gradle-plugin、如果你是使用Mavne构建的系统使用 protobuf-maven-plugin
添加依赖项
plugins { id 'com.google.protobuf' version '0.8.8' }protobuf { protoc { artifact = "com.google.protobuf:protoc:3.11.0" } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0' } } generateProtoTasks { all()*.plugins { grpc {} } } }

编写proto文件 注意proto放置的路径
syntax = "proto3"; package com.mjw.proto; option java_package = "com.mjw.proto"; option java_outer_classname = "PersonProto"; option java_multiple_files = true; service PersonService{ rpc getRealNameByUsername(MyRequest) returns(MyResponse){} } message MyRequest{ string username = 1; }message MyResponse{ string realname = 2; }

小菜鸡带你gRPC入门[Netty系列]
文章图片

在我们进行编译之前我们需要将.proto文件放置到默认路径下,否则无法编译成功。
编译 gradle generateProto
会自动去下载所需工具 ,编译完成查看默认路径下生成的文件【路径可配置】,Java文件生成。成功!
小菜鸡带你gRPC入门[Netty系列]
文章图片

  • 将生成出的文件移到我们的Java目录下。
实现客户端与服务器端通信 最简单的一元RPC实现
? 实现服务端业务方法
public class PersonServiceImpl extends PersonServiceGrpc.PersonServiceImplBase { @Override public void getRealNameByUsername(MyRequest request, StreamObserver responseObserver) { System.out.println("接收到客户端消息: " + request.getUsername()); responseObserver.onNext(MyResponse.newBuilder().setRealname("张三").build()); responseObserver.onCompleted(); } }

? 服务器端
public class GrpcServer { private Server server; //启动 private void start() throws Exception{ this.server = ServerBuilder.forPort(8899).addService(new PersonServiceImpl()).build().start(); System.out.println("服务器启动..."); Runtime.getRuntime().addShutdownHook(new Thread(() ->{ System.out.println("关闭jvm"); GrpcServer.this.stop(); })); } //停止 private void stop(){ if (null != this.server){ this.server.shutdown(); } } //等待 private void awaitTermination() throws Exception{ if (null != this.server){ this.server.awaitTermination(); } } public static void main(String[] args) throws Exception{ GrpcServer server = new GrpcServer(); server.start(); server.awaitTermination(); } }public class GrpcServer { private Server server; //启动 private void start() throws Exception{ this.server = ServerBuilder.forPort(8899).addService(new PersonServiceImpl()).build().start(); System.out.println("服务器启动..."); } //停止 private void stop(){ if (null != this.server){ this.server.shutdown(); } } //等待 private void awaitTermination() throws Exception{ if (null != this.server){ this.server.awaitTermination(); } } public static void main(String[] args) throws Exception{ GrpcServer server = new GrpcServer(); server.start(); server.awaitTermination(); } }

? 客户端
public class GrpcClient { public static void main(String[] args) { ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 8899). usePlaintext().build(); PersonServiceGrpc.PersonServiceBlockingStub blockingStub = PersonServiceGrpc. newBlockingStub(managedChannel); MyResponse myResopnse = blockingStub. getRealNameByUsername(MyRequest.newBuilder().setUsername("李四").build()); System.out.println(myResopnse.getRealname()); } }

? 成功运行成功代码么的问题、运行结果
//服务器端 服务器启动... 接收到客户端消息: 李四 //客户端 张三

? 回调钩子 – 在你中断服务器的时候会触发回调钩子
//回调钩子 Runtime.getRuntime().addShutdownHook(new Thread(() ->{ System.out.println("关闭jvm"); GrpcServer.this.stop(); }));

接下来三种传输实现都在第一种一元rpc上进行修改,GrpcServer代码不再进行修改
服务器流式RPC
? .proto文件
service PersonService{ rpc getRealNameByUsername(MyRequest) returns(MyResponse){}rpc GetPersonByAge(PersonRequest) returns(stream PersonResponse){} } message MyRequest{ string username = 1; }message MyResponse{ string realname = 2; }message PersonRequest{ int32 age = 1; } message PersonResponse{ string name = 1; int32 age = 2; string city = 3; }

? 业务方法实现
@Override public void getPersonByAge(PersonRequest request, StreamObserver responseObserver) { System.out.println("接收到客户端信息" + request.getAge()); responseObserver.onNext(PersonResponse.newBuilder().setName("李四").setAge(20).setCity("苏州").build()); responseObserver.onNext(PersonResponse.newBuilder().setName("王五").setAge(30).setCity("南京").build()); responseObserver.onNext(PersonResponse.newBuilder().setName("赵六").setAge(40).setCity("昆山").build()); responseObserver.onCompleted(); //接收流成功完成的通知,只能被调用一次,必须是最后被调用的方法 }

? 客户端实现
public static void main(String[] args) { ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 8899). usePlaintext().build(); PersonServiceGrpc.PersonServiceBlockingStub blockingStub = PersonServiceGrpc. newBlockingStub(managedChannel); Iterator iterator = blockingStub.getPersonByAge(PersonRequest.newBuilder().setAge(30).build()); while (iterator.hasNext()){ PersonResponse next = iterator.next(); System.out.println(next.getName() +" "+next.getAge() +" "+ next.getCity()); } } --------------------------------------输出结果--------------------------------------- 李四 20 苏州 王五 30 南京 赵六 40 昆山

如果对应 Java 代码其实服务器端返回了一个迭代器对象。客户端对数据进行迭代即可,很容易理解。
客户端流式RPC
? .proto
service PersonService{ rpc getRealNameByUsername(MyRequest) returns(MyResponse){}rpc GetPersonByAge(PersonRequest) returns(stream PersonResponse){}rpc ServerGetPersonSteam(stream PersonRequest) returns(PersonResponseList){} } message MyRequest{ string username = 1; }message MyResponse{ string realname = 2; }message PersonRequest{ int32 age = 1; } message PersonResponse{ string name = 1; int32 age = 2; string city = 3; }message PersonResponseList{ repeated PersonResponse personResponse = 1; }

? 业务方法实现
@Override public StreamObserver serverGetPersonSteam(StreamObserver responseObserver) { return new StreamObserver() { @Override public void onNext(PersonRequest value) { //服务器端每接收到一个消息onNext都会调用一次 System.out.println("接收到客户端消息--onNext: " + value.getAge()); }@Override public void onError(Throwable t) { System.out.println(t.getMessage()); }@Override public void onCompleted() { //客户端消息全部传递完成之后服务器端构造返回的结果对象 PersonResponse personResponse = PersonResponse.newBuilder().setName("张三").setAge(11).setCity("北京").build(); PersonResponse personResponse1 = PersonResponse.newBuilder().setName("李四").setAge(22).setCity("上海").build(); PersonResponseList personResponseList = PersonResponseList.newBuilder(). addPersonResponse(personResponse).addPersonResponse(personResponse1).build(); responseObserver.onNext(personResponseList); responseObserver.onCompleted(); //表示服务器端的所有流程都结束了 } }; }

? 客户端实现
public static void main(String[] args) { ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 8899). usePlaintext().build(); /*PersonServiceGrpc.PersonServiceBlockingStub blockingStub = PersonServiceGrpc. newBlockingStub(managedChannel); */ //这是异步的 PersonServiceGrpc.PersonServiceStub stub = PersonServiceGrpc.newStub(managedChannel); StreamObserver personStreamObserver = new StreamObserver() { @Override public void onNext(PersonResponseList value) { value.getPersonResponseList().forEach(personResponse ->{ System.out.println(personResponse.getName()); System.out.println(personResponse.getAge()); System.out.println(personResponse.getCity()); System.out.println("----------------------------"); }); }@Override public void onError(Throwable t) { System.out.println(t.getMessage()); }@Override public void onCompleted() { System.out.println("onCompleted:流程结束"); } }; //只要客户端向服务器端发送流式的请求,那这个请求就一定是异步的StreamObserver streamObserver = stub.serverGetPersonSteam(personStreamObserver); streamObserver.onNext(PersonRequest.newBuilder().setAge(10).build()); streamObserver.onNext(PersonRequest.newBuilder().setAge(20).build()); streamObserver.onNext(PersonRequest.newBuilder().setAge(30).build()); streamObserver.onNext(PersonRequest.newBuilder().setAge(40).build()); streamObserver.onCompleted(); //以为 stub 是异步的会直接将程序执行完成,属于我们这里休眠一段时间 try { Thread.sleep(10000); }catch (InterruptedException e){ e.printStackTrace(); } } ---------------------------------------输出结果-------------------------------------- 张三 11 北京 ---------------------------- 李四 22 上海 ---------------------------- onCompleted:流程结束

PersonServiceGrpc.newBlockingStub(managedChannel); 同步阻塞,在这里我们找不到我们要调用的serverGetPersonSteam方法。
小菜鸡带你gRPC入门[Netty系列]
文章图片

PersonServiceGrpc.newStub(managedChannel); 异步非阻塞 只有这才能找到我们要调用的方法
小菜鸡带你gRPC入门[Netty系列]
文章图片

双向流式PRC
? .proto
syntax = "proto3"; package com.mjw.proto; option java_package = "com.mjw.proto"; option java_outer_classname = "PersonProto"; option java_multiple_files = true; service PersonService{ rpc BothWayCommunication(stream StreamRequest) returns(stream StreamResponse){} }message StreamRequest{ string requestMessage =1; }message StreamResponse{ string responseMessage = 1; }

? 服务实现
@Override public StreamObserver> bothWayCommunication(StreamObserver> responseObserver) { return new StreamObserver>() { @Override public void onNext(StreamRequest value) { System.out.println(value.getRequestMessage()); responseObserver.onNext(StreamResponse.newBuilder(). setResponseMessage("服务器端响应").build()); }@Override public void onError(Throwable t) { System.out.println(t.getMessage()); }@Override public void onCompleted() { responseObserver.onCompleted(); } }; }

? 客户端实现
public static void main(String[] args) { ManagedChannel managedChannel = ManagedChannelBuilder. forAddress("localhost", 8899).usePlaintext().build(); PersonServiceGrpc.PersonServiceStub stub = PersonServiceGrpc. newStub(managedChannel); StreamObserver> streamObserver = stub. bothWayCommunication(new StreamObserver>() { @Override public void onNext(StreamResponse value) { System.out.println(value.getResponseMessage()); }@Override public void onError(Throwable t) { System.out.println(t.getMessage()); }@Override public void onCompleted() { System.out.println("onCompleted"); } }); for (int i =0; i<3; i++) { streamObserver.onNext(StreamRequest.newBuilder(). setRequestMessage("客户端请求" + i).build()); }try { Thread.sleep(10000); }catch (InterruptedException e){ e.printStackTrace(); } } ----------------------------------------输出结果-------------------------------------- 服务器端 --> 服务器启动... 客户端请求0 客户端请求1 客户端请求2客户端 --> 服务器端响应 服务器端响应 服务器端响应

【小菜鸡带你gRPC入门[Netty系列]】好到现在 gRpc的四种传输方式都完成啦。。。 加油

    推荐阅读