为什么去使用gRPC
使用gRPC,我们可以在.proto文件中定义我们的服务,并以任何gRPC支持的语言实现客户端和服务器。 使用gRPC还具有protocol buffer的所有优点,包括高效的序列化,简单的IDL和简单的接口更新。
自己认为rpc框架的优点主要在于,性能好(使用高效的序列化框架,tcp传输协议),跨语言(服务端和客户端可以使用不同的语言开发)。
引入依赖
使用maven构建项目,maven依赖:
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.4.0</version>
</dependency>
使用Gradle构建项目,Gradle依赖:
compile 'io.grpc:grpc-netty:1.4.0'
compile 'io.grpc:grpc-protobuf:1.4.0'
compile 'io.grpc:grpc-stub:1.4.0'
Android开发,使用grpc-okhttp
代替grpc-netty
,使用grpc-protobuf-lite
,或者grpc-protobuf-nano
代替grpc-protobuf
compile 'io.grpc:grpc-okhttp:1.4.0'
compile 'io.grpc:grpc-protobuf-lite:1.4.0'
compile 'io.grpc:grpc-stub:1.4.0'
使用插件根据指定的.proto
文件生成代码,要将.proto
文件放在src/main/proto
和src/test/proto
目录下,这样插件就可以找到.proto文件并且生成代码。
使用maven插件如下:
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.4.1.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.4.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
或者使用gradle插件:
apply plugin: 'java'
apply plugin: 'com.google.protobuf'
buildscript {
repositories {
mavenCentral()
}
dependencies {
// ASSUMES GRADLE 2.12 OR HIGHER. Use plugin version 0.7.5 with earlier
// gradle versions
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
}
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.2.0"
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.4.0'
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
在build.gradle
中加入相应的插件之后,idea出现gradle插件图形显示
如果要将.proto文件放在其他路径,可以加入下面的指令:
sourceSets {
main {
proto {
// In addition to the default 'src/main/proto'
srcDir 'src/main/protobuf'
srcDir 'src/main/protocolbuffers'
// In addition to the default '**/*.proto' (use with caution).
// Using an extension other than 'proto' is NOT recommended,
// because when proto files are published along with class files, we can
// only tell the type of a file from its extension.
include '**/*.protodevel'
}
java {
...
}
}
test {
proto {
// In addition to the default 'src/test/proto'
srcDir 'src/test/protocolbuffers'
}
}
}
编写.proto文件
在src/main/proto
目录下编写Student.proto
文件
syntax = "proto3";
package com.zhihao.miao.proto;
option java_package = "com.zhihao.miao.proto";
option java_outer_classname ="StudentProto";
//将属性生成多个文件,便于代码管理
option java_multiple_files = true;
//定义rpc方法
service StudentService{
//一元RPC
rpc GetRealNameByUsername(MyRequest) returns (MyResponse){}
}
message MyRequest{
string username = 1;
}
message MyResponse{
string realname = 2;
}
使用gradle插件编译
gradle generateProto
将生成的代码copy到main/java的制定的包下,生成的类有以下几个,MyRequest
,MyRequestOrBuilder
,MyResponse
,MyResponseOrBuilder
,StudentProto
是GRPC消息传输的Message,后面的一个StudentServiceGrpc
类是远程调用的接口,服务端要重写.proto中的方法要实现其内部类StudentServiceImplBase
是
编写服务器代码
服务端业务接口实现
package com.zhihao.miao.gprc;
import com.zhihao.miao.proto.*;
import io.grpc.stub.StreamObserver;
public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase{
//第一个对象是请求参数对象,第二个参数是向客户端返回结果的对象,方法定义不返回值,而是通过responseObserver去返回值
@Override
public void getRealNameByUsername(MyRequest request, StreamObserver<MyResponse> responseObserver) {
System.out.println("接收到客户端信息: "+request.getUsername());
//onNext,onError,onCompletedB表示方法都是回调方法
responseObserver.onNext(MyResponse.newBuilder().setRealname("王超").build());
//onCompletedB表示方法方法执行完毕
responseObserver.onCompleted();
}
}
编写服务器代码
编写服务器端代码,参考快速入门的服务端代码,HelloWorldServer.java
类
cd /Users/naeshihiroshi/study/studySummarize/netty/grpc-java/examples
cd src/main/java/io/grpc/examples/helloworld
参考
服务器端代码:
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
public class GrpcServer {
private Server server;
//编写服务启动方法
private void start() throws IOException{
//增加实际业务代码的实列,如果使用spring的话可以直接将StudentServiceImpl对象注入进来
this.server = ServerBuilder.forPort(8899).addService(new StudentServiceImpl()).build().start();
System.out.println("server start!");
//jvm回调钩子的作用,Runtime.getRuntime()可以获得java一些运行期间的一些信息。
//不管程序是正常关闭还是异常终端,在jvm关闭的时候做一些清理工作
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("关闭jvm");
GrpcServer.this.stop();
System.out.println("服务关闭");
}));
System.out.println("执行到这里");
}
//停止服务器
private void stop(){
if(null != this.server){
this.server.shutdown();
}
}
//服务器阻塞
private void blockUntilShutdown() throws InterruptedException {
if(null != this.server){
this.server.awaitTermination();
//this.server.awaitTermination(3000, TimeUnit.MILLISECONDS);
}
}
public static void main(String[] args) throws IOException,InterruptedException{
GrpcServer server = new GrpcServer();
server.start();;
server.blockUntilShutdown();
}
}
客户端代码:
import com.zhihao.miao.proto.*;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
import java.time.LocalDateTime;
import java.util.Iterator;
public class GrpcClient {
public static void main(String[] args) throws Exception{
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",8899)
//使用TLS来连接服务端
.usePlaintext(true).build();
//定义一个stub,用于实际和服务端连接的对象blockingStub
StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel);
MyResponse response =blockingStub.getRealNameByUsername(MyRequest.newBuilder()
.setUsername("chao.wang").build());
System.out.println(response.getRealname());
}
}
运行客户端与服务器端,服务器端控制台打印:
server start!
执行到这里
接收到客户端信息: chao.wang
客户端控制台打印:
王超
Disconnected from the target VM, address: '127.0.0.1:63197', transport: 'socket'
Process finished with exit code 0
关闭服务器,服务器控制台上打印,执行jvm回调钩子。:
关闭jvm
服务关闭