gRPC 入门 java 示例 前言 入职新公司一周后,一直在干看代码,感觉理解起来进度很慢,现在终于有任务给我了
是需要写一个基于 gRPC 的接口
开始学习起来
学东西首先要看的就是官网
https://grpc.io/
这是 java入门教程: https://grpc.io/docs/languages/java/quickstart/
Intro 看官网就够够了: https://grpc.io/docs/what-is-grpc/introduction/
In gRPC, a client application can directly call a method on a server application on a different machine as if it were a local object, making it easier for you to create distributed applications and services. As in many RPC systems, gRPC is based around the idea of defining a service, specifying the methods that can be called remotely with their parameters and return types. On the server side, the server implements this interface and runs a gRPC server to handle client calls. On the client side, the client has a stub (referred to as just a client in some languages) that provides the same methods as the server.
gRPC clients and servers can run and talk to each other in a variety of environments - from servers inside Google to your own desktop - and can be written in any of gRPC’s supported languages. So, for example, you can easily create a gRPC server in Java with clients in Go, Python, or Ruby. In addition, the latest Google APIs will have gRPC versions of their interfaces, letting you easily build Google functionality into your applications.
RPC 就不赘述了, 说一下 gRPC
gRPC 就是 RPC 的一种实现,简单来说就是
定义了一份通信标准
server 端去实现这个通信标准并且启动 gRPC 服务器去处理 client 端的请求.
client 端有一份 stub(存根),提供和 server 端一样的方法
server 和 client 端可以是不同机器不同环境不同语言下
Protocol Buffers 众所周知,RPC 协议一个重点就是商榷传输的规则,gRPC 采用的是 Protocol Buffers 的方式
关于 Protocol Buffers 的具体情况有机会再写一篇
简而言之就是写了一份 .proto文件,根据这个文件利用插件生成 server 端和 client 端的对应语言的代码
生成代码需要下载安装 protoc
下载地址见 github: https://github.com/protocolbuffers/protobuf/releases , 需要将/src添加到环境变量
protocol buffer java 生成教程: https://developers.google.com/protocol-buffers/docs/reference/java-generated
java 示例一: 官网(太监) 我直接跟着官网走的,这里有一个https://grpc.io/docs/languages/java/basics/
编写protoc 文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 syntax = "proto3" ; option java_multiple_files = true ;option java_package = "io.grpc.examples.routeguide" ;option java_outer_classname = "RouteGuideProto" ;option objc_class_prefix = "RTG" ;package routeguide;service RouteGuide { rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} } message Point { int32 latitude = 1 ; int32 longitude = 2 ; } message Rectangle { Point lo = 1 ; Point hi = 2 ; } message Feature { string name = 1 ; Point location = 2 ; } message FeatureDatabase { repeated Feature feature = 1 ; } message RouteNote { Point location = 1 ; string message = 2 ; } message RouteSummary { int32 point_count = 1 ; int32 feature_count = 2 ; int32 distance = 3 ; int32 elapsed_time = 4 ; }
编译生成 java 类 生成 proto 对应的代码 刚开始我直接 protoc
1 protoc --java_out=src/main/java/ src/main/proto/route_guide.proto
仅仅生成了proto 文件对应的代码
生成 grpc 代码 网上google了之后发现是需要使用插件才可以生成 grpc 代码
maven 仓库: https://repo1.maven.org/maven2/io/grpc/protoc-gen-grpc-java/1.38.1/
链接是 1.38 .1版本的
发现都是.exe 结尾的,我是 mac 怎么办,后来查询到只需要给予执行权限就行了
找到对应版本下载后,执行
1 chmod u+x protoc-gen-grpc-java-1.38.1-osx-x86_64.exe
然后执行
1 2 3 4 5 protoc \ > --java_out=src/main/java/ \ > --plugin=protoc-gen-grpc-java=/Users/yiqing/Downloads/protoc-gen-grpc-java-1.38.1-osx-x86_64.exe \ > --grpc-java_out=src/main/java/ \ > src/main/proto/route_guide.proto
即可发现新增了关键的 RouteGuideGrpc.java 文件
好的,终于可以继续看教程了,下一步就是编写服务端和客户端的代码了
好的,鄙人不才,官网的示例感觉根本运行不起来,教程很过时
所以重新做了一个简单的示例
java 示例二: 简洁版 实现简单的客户端上传两个int,返回两个 int 之和
创建 maven 项目 话不多说
修改 pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > org.example</groupId > <artifactId > grpcjava</artifactId > <version > 1.0-SNAPSHOT</version > <properties > <maven.compiler.source > 1.8</maven.compiler.source > <maven.compiler.target > 1.8</maven.compiler.target > <java.version > 1.8</java.version > <grpc.version > 1.34.1</grpc.version > <protobuf.version > 3.12.0</protobuf.version > <protoc.version > 3.12.0</protoc.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > io.grpc</groupId > <artifactId > grpc-bom</artifactId > <version > ${grpc.version}</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement > <dependencies > <dependency > <groupId > io.grpc</groupId > <artifactId > grpc-netty-shaded</artifactId > </dependency > <dependency > <groupId > io.grpc</groupId > <artifactId > grpc-protobuf</artifactId > </dependency > <dependency > <groupId > io.grpc</groupId > <artifactId > grpc-stub</artifactId > </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.6.2</version > </extension > </extensions > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <configuration > <excludes > <exclude > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </exclude > </excludes > </configuration > </plugin > <plugin > <groupId > org.xolstice.maven.plugins</groupId > <artifactId > protobuf-maven-plugin</artifactId > <version > 0.6.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 > <protoSourceRoot > src/main/resources/proto</protoSourceRoot > </configuration > <executions > <execution > <goals > <goal > compile</goal > <goal > compile-custom</goal > </goals > </execution > </executions > </plugin > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <configuration > <source > 1.8</source > <target > 1.8</target > </configuration > </plugin > </plugins > </build > </project >
这个方法是采用 maven 编译生成 grpc 代码的
创建proto 文件 注意 pom 中的 protoSourceRoot 目录,在该目录下新建 proto 文件, add.proto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 syntax = "proto3" ; option java_package = "zone.yiqing.grpc" ;option java_outer_classname = "AddServiceProto" ;option java_multiple_files = true ;service AddService { rpc add(AddRequest) returns (AddReply) {} } message AddRequest { int32 a = 1 ; int32 b = 2 ; } message AddReply { int32 res = 1 ; }
其中标注了语法为proto3
根据 proto 生成类 在 maven 中直接点击 maven install
但是看到项目中没有生成任何文件
去 target 中,查看 generated-sources 中,protobuf 文件夹
其中重要的是我们生成的 AddServiceImplBase 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static abstract class AddServiceImplBase implements io .grpc.BindableService { public void add (zone.yiqing.grpc.AddRequest request, io.grpc.stub.StreamObserver<zone.yiqing.grpc.AddReply> responseObserver) { asyncUnimplementedUnaryCall(getAddMethod(), responseObserver); } @java .lang.Override public final io.grpc.ServerServiceDefinition bindService () { return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) .addMethod( getAddMethod(), asyncUnaryCall( new MethodHandlers < zone.yiqing.grpc.AddRequest, zone.yiqing.grpc.AddReply>( this , METHODID_ADD))) .build(); } }
可以看到 add 方法已经添加
传入参数为 AddRequest
返回参数为 void 的原因: gRPC 的入参和返回值都在参数里,即io.grpc.stub.StreamObserver<zone.yiqing.grpc.AddReply> responseObserver
.这里通过一个对象去监听了返回值
真正实现 addService 的话,需要继承这个AddServiceImplBase
并且重写方法
编写 Server 端
在项目中新建一个类 AddService
,写一个 main 方法,并继承AddServiceGrpc.AddServiceImplBase
可见尽管在 target 中,也是可以继承的
重写 add 方法, idea 快捷键control + o
删掉里面的东西,添加自己写的方法,返回两者之和
注意 AddReply 的构造方法
在 main 方法中启动 server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class AddServer extends AddServiceGrpc .AddServiceImplBase { public static void main (String[] args) throws IOException { ServerBuilder .forPort(7777 ) .addService(new AddServer ()) .build() .start(); System.out.println("server on 7777" ); } @Override public void add (AddRequest request, StreamObserver<AddReply> responseObserver) { int add = myAdd(request.getA(), request.getB()); responseObserver.onNext(AddReply.newBuilder().setRes(add).build()); responseObserver.onCompleted(); } public int myAdd (int a, int b) { return a + b; } }
编写 Client 端
在项目中新建 AddClient 类
添加一个构造类,其中创建一个 channel,并且创建一个 stub
stub是用来请求的,请求的时候 client 是通过 将参数传给 stub,stub 传给服务端的
在 main 方法中创建 channel 和发起请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class AddClient { AddServiceBlockingStub stub; ManagedChannel channel; public static void main (String[] args) { AddClient client = new AddClient (); int a = 1 ; int b = 1 ; AddReply addReply = client.stub.add(AddRequest.newBuilder().setA(a).setB(b).build()); System.out.println(addReply.getRes()); } public AddClient () { channel = ManagedChannelBuilder .forAddress("127.0.0.1" , 7777 ) .usePlaintext() .build(); stub = AddServiceGrpc.newBlockingStub(channel); } }
启动 server 运行 server,发现 server 立即退出,说明应该是新开了一个线程 去创建 server,当主线程结束后也退出了.
修改一下代码添加 while(true){}
可以看到端口已经在运行了:
1 2 3 yiqings-laptop:~ yiqing$ lsof -i:7777 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 36278 yiqing 64u IPv6 0x37ddc7b92240c489 0t0 TCP *:cbt (LISTEN)
启动 client 可以看到控制台打印出了结果 2
done!