Protobuf & gRPC简介
1、Protobuf
1.1、概念
Protobuf是Google protocol buffer的简称,是一种语言中立、平台无关、易于扩展的结构化数据序列化技术,可用于数据传输、存储等领域。
与Protoful类似的序列化技术还有XML、JSON、Thrift等,但Protoful更快、更小、更简单,且具备良好的兼容性。
图片源于网络:展示了Protoful与其他同类技术之间的序列化效率(时间)横向比对结果
图片源于网络:展示了Protoful与其他同类技术之间的序列化结果(空间)横向比对结果
Protoful的数据格式使用Protocol buffer language来定义,Protocol buffer language存储在后缀名为 .proto
的文件中。Protocol buffer language目前有两个版本:proto2和proto3,两个版本之间差异较大,推荐使用proto3版本
此外Google还提供Protocol buffer编译器 protoc
,可以根据 .proto
文件生成各种语言的实现代码。
1.2、使用
1.2.1、编写.proto
// 标识使用的版本,默认proto2
syntax = "proto3";
// 定义包名
package tutorial;
// protoc将生成多个java文件,顶级message、enum、service将作为单独文件存在
option java_multiple_files = true;
// Java文件的包路径
option java_package = "com.chenlei.tutorial";
// 定义外层类名称,如果没有java_multiple_files,将生成一个单独Java文件,message等其他内容以内部类存在
option java_outer_classname = "AddressBookProtos";
// 定义一个top-level message
message Person {
// 定义一个字段:rule type name=uniqueNumber
string name = 1;
int32 id = 2;
string email = 3;
// 定义一个枚举,枚举的第一项的值必须为0,0将作为该枚举的默认值使用
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
// 定义一个内部message,嵌套在Person内部
message PhoneNumber {
string number = 1;
// 引用枚举类型
PhoneType type = 2;
}
// 定义一个自定义类型的字段,引用PhoneNumber
repeated PhoneNumber phones = 4;
}
// 定义一个top-level message
message AddressBook {
// rule type name=uniqueNumber
repeated Person people = 1;
}
1.2.2、安装protoc
- 下载页面:https://github.com/google/protobuf/releases
- 下载合适的版本,例如:protoc-3.5.1-osx-x86_64.zip
- 解压后将$DownloadPath/bin/protoc复制到\$PATH路径下,给执行权限
- 测试安装:
LiondeMacBook-Pro:Downloads lion$ protoc --version
libprotoc 3.5.1
1.2.3、测试protoful
- 根据addressbook.proto文件生成Java代码
LiondeMacBook-Pro:protobuf lion$ protoc -I=. --java_out=src/main/java/ addressbook.proto
LiondeMacBook-Pro:protobuf lion$ ll src/main/java/com/chenlei/tutorial/
-rw-r--r-- 1 lion staff 24524 May 30 14:03 AddressBook.java
-rw-r--r-- 1 lion staff 946 May 30 14:03 AddressBookOrBuilder.java
-rw-r--r-- 1 lion staff 3922 May 30 14:03 AddressBookProtos.java
-rw-r--r-- 1 lion staff 56708 May 30 14:03 Person.java
-rw-r--r-- 1 lion staff 1468 May 30 14:03 PersonOrBuilder.java
- 添加maven依赖
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.5.1</version>
</dependency>
- 编写AddPerson,测试将数据序列化到文件
package com.chenlei;
import com.chenlei.tutorial.AddressBook;
import com.chenlei.tutorial.Person;
import java.io.*;
/**
* @Author: 陈磊
* @Date: 2018/5/30
* @Description:
*/
public class AddPerson {
// 这个方法根据用户的输入信息来填充Person对象
public static Person promptForAddress(BufferedReader stdin, PrintStream stdout) throws IOException {
Person.Builder person = Person.newBuilder();
stdout.print("Enter person ID: ");
person.setId(Integer.valueOf(stdin.readLine()));
stdout.print("Enter name: ");
person.setName(stdin.readLine());
stdout.print("Enter email address (blank for none): ");
String email = stdin.readLine();
if (email.length() > 0) {
person.setEmail(email);
}
while (true) {
stdout.print("Enter a phone number (or leave blank to finish): ");
String number = stdin.readLine();
if (number.length() == 0) {
break;
}
Person.PhoneNumber.Builder phoneNumber = Person.PhoneNumber.newBuilder();
phoneNumber.setNumber(number);
stdout.print("Is this a mobile, home, or work phone? ");
String type = stdin.readLine();
if (type.equalsIgnoreCase("mobile")) {
phoneNumber.setType(Person.PhoneType.MOBILE);
} else if (type.equalsIgnoreCase("home")) {
phoneNumber.setType(Person.PhoneType.HOME);
} else if (type.equalsIgnoreCase("work")) {
phoneNumber.setType(Person.PhoneType.WORK);
} else {
stdout.println("Unknown phone type. Using default.");
}
person.addPhones(phoneNumber);
}
return person.build();
}
public static void main(String[] args) throws IOException {
String filename = "./addressBook.dat";
File file = new File(filename);
if (!file.exists()) {
file.createNewFile();
}
AddressBook.Builder addressBook = AddressBook.newBuilder();
try {
// 从已有文件中读入数据
addressBook.mergeFrom(new FileInputStream(filename));
} catch (IOException e) {
e.printStackTrace();
}
// 调用promptForAddress方法创建Person,将person添加到addressBook
addressBook.addPeople(promptForAddress(new BufferedReader(new InputStreamReader(System.in)), System.out));
FileOutputStream outputStream = new FileOutputStream(filename);
// 调用writeTo将序列化数据写入outputStream
addressBook.build().writeTo(outputStream);
outputStream.close();
}
}
- 编写ListPerson,测试从序列化文件中解析出数据
package com.chenlei;
import com.chenlei.tutorial.AddressBook;
import com.chenlei.tutorial.Person;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @Author: 陈磊
* @Date: 2018/5/30
* @Description:
*/
public class ListPerson {
// 打印addressBook信息
public static void print (AddressBook addressBook) {
for (Person person : addressBook.getPeopleList()) {
System.out.println("Person ID: " + person.getId());
System.out.println(" Name: " + person.getName());
System.out.println(" E-mail address: " + person.getEmail());
for (Person.PhoneNumber phoneNumber : person.getPhonesList()) {
switch (phoneNumber.getType()) {
case MOBILE:
System.out.print(" Mobile phone #: ");
break;
case HOME:
System.out.print(" Home phone #: ");
break;
case WORK:
System.out.print(" Work phone #: ");
break;
}
System.out.println(phoneNumber.getNumber());
}
}
}
public static void main(String[] args) throws IOException {
String filename = "./addressBook.dat";
// 调用parseFrom方法解析序列化数据
AddressBook addressBook = AddressBook.parseFrom(new FileInputStream(filename));
// 调用print方法打印解析结果
print(addressBook);
}
}
2、gRPC
2.1、概念
gRPC是Google开源的RPC框架,使用HTTP/2协议基于Protobuf开发,沿袭了Protobuf的高效、简洁以及平台无关性和语言无关性。
gRPC通过在 .proto
文件中的service定义,message将作为参数和返回值来使用。通过 protoc
,可以方便的将 .proto
文件编译成各种gRPC支持的客户端和服务端代码,客户端可以像调用本地代码一样调用语言环境完全不同的服务端。
2.2、使用
2.2.1、普通gRPC
2.2.1.1、编写.proto
- 事例文件:/Users/lion/IdeaProjects/X-Project/grpc/src/main/proto/helloworld.proto
syntax = "proto3";
package tutorial;
option java_multiple_files = true;
option java_package = "com.chenlei.tutorial";
option java_outer_classname = "HelloWorldProto";
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
2.2.1.2、生成代码
这里使用maven插件来生成gRPC的代码,pom.xml内容如下:
<?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>com.chenlei</groupId>
<artifactId>grpc</artifactId>
<version>1.0-SNAPSHOT</version>
<name>grpc</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.12.0</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.12.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
执行protobuf:compile插件和protobuf:compile-custom插件分别生存protobuf代码和gRPC代码
2.2.1.3、测试gRPC
- 编写服务端测试代码:
package com.chenlei.tutorial;
import io.grpc.stub.StreamObserver;
/**
* @Author: 陈磊
* @Date: 2018/5/31
* @Description: 服务端逻辑实现
*/
public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();
// 响应
responseObserver.onNext(reply);
// 结束
responseObserver.onCompleted();
}
}
package com.chenlei.tutorial;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
import java.util.logging.Logger;
/**
* @Author: 陈磊
* @Date: 2018/5/31
* @Description: 启动服务端监听
*/
public class GreeterService {
private static final Logger logger = Logger.getLogger(GreeterService.class.getName());
private static final int port = 50051;
private Server server;
// 启动服务,监听50051端口
private void start () throws IOException {
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() {
System.out.println("*** shutting down gRPC server since JVM is shutting down");
GreeterService.this.stop();
System.out.println("*** server shut down");
}
});
}
// 停止服务
private void stop () {
if (server != null) {
server.shutdown();
}
}
// 等待主线程结束
private void blockUntilShutdown () throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
final GreeterService service = new GreeterService();
service.start();
service.blockUntilShutdown();
}
}
- 编写客户端测试代码:
package com.chenlei.tutorial;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* @Author: 陈磊
* @Date: 2018/5/31
* @Description:
*/
public class GreeterClient {
public static final Logger logger = Logger.getLogger(GreeterService.class.getName());
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
public GreeterClient(ManagedChannel channel) {
this.channel = channel;
// 获得stub
this.blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public GreeterClient(String host, int port) {
// 创建channel
this (ManagedChannelBuilder.forAddress(host, port).usePlaintext().build());
}
// 关闭channel
public void shutdown () throws InterruptedException {
this.channel.shutdown().awaitTermination(5, TimeUnit.MINUTES);
}
// 利用stub调用sayHello
public void greet (String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply reply = blockingStub.sayHello(request);
logger.info("Greeting: " + reply.getMessage());
}
public static void main(String[] args) throws InterruptedException {
GreeterClient client = new GreeterClient("localhost", 50051);
try {
client.greet("world");
} finally {
client.shutdown();
}
}
}
2.2.2、stream gRPC
2.2.2.1、编写.proto
- 事例文件:/Users/lion/IdeaProjects/X-Project/grpc/src/main/proto/hello_streaming.proto
syntax = "proto3";
package stream;
option java_multiple_files = true;
option java_package = "com.chenlei.stream";
option java_outer_classname = "HelloWorldProto";
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
// 请求和响应都可以配置stream,也可以只配置一边
service Greeter {
rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
}
2.2.2.2、生产代码
操作步骤同上:
2.2.2.3、测试gRPC
- 编写服务端测试代码
package com.chenlei.stream;
import io.grpc.stub.StreamObserver;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @Author: 陈磊
* @Date: 2018/5/31
* @Description:
*/
public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
public static final Logger logger = Logger.getLogger(GreeterService.class.getName());
@Override
public StreamObserver<HelloRequest> sayHello(final StreamObserver<HelloReply> responseObserver) {
return new StreamObserver<HelloRequest>() {
@Override
public void onNext(HelloRequest helloRequest) {
// 因为stream response,可以多次调用onNext
for (int i = 0; i < 3; i++) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + helloRequest.getName()).build();
responseObserver.onNext(reply);
// 每次响应设置两秒等待时间
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void onError(Throwable throwable) {
logger.log(Level.WARNING, "sayHello cancelled");
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
}
package com.chenlei.stream;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
import java.util.logging.Logger;
/**
* @Author: 陈磊
* @Date: 2018/5/31
* @Description:
*/
public class GreeterService {
private static final Logger logger = Logger.getLogger(GreeterService.class.getName());
private static final int port = 50051;
private Server server;
// 启动服务,监听50051端口
private void start () throws IOException {
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() {
System.out.println("*** shutting down gRPC server since JVM is shutting down");
GreeterService.this.stop();
System.out.println("*** server shut down");
}
});
}
// 停止服务
private void stop () {
if (server != null) {
server.shutdown();
}
}
// 等待主线程结束
private void blockUntilShutdown () throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
final GreeterService service = new GreeterService();
service.start();
service.blockUntilShutdown();
}
}
- 编写客户端测试代码
package com.chenlei.stream;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* @Author: 陈磊
* @Date: 2018/5/31
* @Description:
*/
public class GreeterClient {
private static final Logger logger = Logger.getLogger(GreeterService.class.getName());
private final ManagedChannel channel;
public GreeterClient(ManagedChannel channel) {
this.channel = channel;
}
public GreeterClient(String host, int port) {
this (ManagedChannelBuilder.forAddress(host, port).usePlaintext().build());
}
public void shutdown () throws InterruptedException {
this.channel.shutdown().awaitTermination(5, TimeUnit.MINUTES);
}
public CountDownLatch greet (String name) {
// 设置线程等待,只到error或者completed
final CountDownLatch countDownLatch = new CountDownLatch(1);
StreamObserver<HelloRequest> requestStreamObserver = GreeterGrpc.newStub(channel).sayHello(new StreamObserver<HelloReply>() {
@Override
public void onNext(HelloReply helloReply) {
logger.info("Got message " + helloReply.getMessage());
}
@Override
public void onError(Throwable throwable) {
logger.warning("Greet Failed: " + Status.fromThrowable(throwable));
countDownLatch.countDown();
}
@Override
public void onCompleted() {
logger.info("Finished greet");
countDownLatch.countDown();
}
});
// 连续发送请求
for (int i = 0; i < 3; i++) {
logger.info("Will try to greet " + name + " + " + i + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name + " + " + i).build();
requestStreamObserver.onNext(request);
}
// 调用completed之前,等待十秒
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
requestStreamObserver.onCompleted();
return countDownLatch;
}
public static void main(String[] args) throws InterruptedException {
GreeterClient client = new GreeterClient("localhost", 50051);
try {
CountDownLatch countDownLatch = client.greet("world");
if (!countDownLatch.await(1, TimeUnit.MINUTES)) {
logger.warning("Greet can not finish within 1 minutes or greet failed");
}
} finally {
client.shutdown();
}
}
}
2.2.3、启用TLS
2.2.3.1、添加maven依赖
<?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>com.chenlei</groupId>
<artifactId>grpc</artifactId>
<version>1.0-SNAPSHOT</version>
<name>grpc</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>2.0.7.Final</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.12.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.2.3.2、改造代码:两种实现方式
- 第一种方式:使用
SelfSignedCertificate
,改造上文中的普通gRPC代码:
package com.chenlei.pki;
import com.chenlei.tutorial.GreeterImpl;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.logging.Logger;
/**
* @Author: 陈磊
* @Date: 2018/5/31
* @Description:
*/
public class GreeterService {
public static final Logger logger = Logger.getLogger(GreeterService.class.getName());
private Server server;
private final String host;
private final int port;
public GreeterService(String host, int port) {
this.host = host;
this.port = port;
}
private void start() throws IOException, CertificateException {
// 服务端使用自签名证书
SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate();
// 添加数据传输安全证书
server = ServerBuilder.forPort(port).useTransportSecurity(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()).addService(new GreeterImpl()).build().start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
GreeterService.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop () {
if (server != null) {
server.shutdown();
}
}
private void blockUntilShutdown () throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
public static void main(String[] args) throws InterruptedException, IOException, CertificateException {
GreeterService service = new GreeterService("127.0.0.1", 8443);
service.start();
service.blockUntilShutdown();
}
}
package com.chenlei.pki;
import com.chenlei.tutorial.GreeterGrpc;
import com.chenlei.tutorial.HelloReply;
import com.chenlei.tutorial.HelloRequest;
import io.grpc.ManagedChannel;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import javax.net.ssl.SSLException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* @Author: 陈磊
* @Date: 2018/5/31
* @Description:
*/
public class GreeterClient {
public static final Logger logger = Logger.getLogger(GreeterClient.class.getName());
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
public GreeterClient(ManagedChannel channel) {
this.channel = channel;
this.blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public GreeterClient(String host, int port, SslContextBuilder sslContextBuilder) throws SSLException {
this (NettyChannelBuilder.forAddress(host, port).negotiationType(NegotiationType.TLS).sslContext(sslContextBuilder.build()).build());
}
private static SslContextBuilder getSslContextBuilder () {
return GrpcSslContexts.forClient().ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE).trustManager(InsecureTrustManagerFactory.INSTANCE);
}
public void shutdown () throws InterruptedException {
this.channel.shutdown().awaitTermination(5, TimeUnit.MINUTES);
}
public void greet (String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply reply = blockingStub.sayHello(request);
logger.info("Greeting: " + reply.getMessage());
}
public static void main(String[] args) throws SSLException, InterruptedException {
GreeterClient client = new GreeterClient("127.0.0.1", 8443, GreeterClient.getSslContextBuilder());
try {
client.greet("world");
} finally {
client.shutdown();
}
}
}
- 第二种方式:提供自定义证书,改造上文中的普通gRPC代码:
LiondeMBP:tls lion$ pwd
/Users/lion/IdeaProjects/X-Project/grpc/src/main/resources/tls
LiondeMBP:tls lion$ ll
-rw------- 1 lion staff 1679 Jun 2 15:09 ca-key.pem
-rw-r--r-- 1 lion staff 1127 Jun 2 15:09 ca.pem
-rw-r--r-- 1 lion staff 241 Jun 2 15:09 grpc-client-key-pkcs8.pem
-rw------- 1 lion staff 227 Jun 2 15:09 grpc-client-key.pem
-rw-r--r-- 1 lion staff 916 Jun 2 15:09 grpc-client.pem
-rw-r--r-- 1 lion staff 241 Jun 2 15:09 grpc-server-key-pkcs8.pem
-rw------- 1 lion staff 227 Jun 2 15:09 grpc-server-key.pem
-rw-r--r-- 1 lion staff 989 Jun 2 15:09 grpc-server.pem
使用cfssl生成证书的脚本:
[root@node-x pki]# cat ca-config.json { "signing": { "default": { "expiry": "43800h" }, "profiles": { "server": { "expiry": "43800h", "usages": [ "signing", "key encipherment", "server auth" ] }, "client": { "expiry": "43800h", "usages": [ "signing", "key encipherment", "client auth" ] }, "peer": { "expiry": "43800h", "usages": [ "signing", "key encipherment", "server auth", "client auth" ] } } } } [root@node-x pki]# cat ca-csr.json { "CN": "gRPC", "key": { "algo": "rsa", "size": 2048 } } [root@node-x pki]# cfssl gencert -initca ca-csr.json | cfssljson -bare ca - [root@node-x pki]# cat grpc-server.json { "CN": "gRPC-server", "hosts": [ "127.0.0.1", "localhost" ], "key": { "algo": "ecdsa", "size": 256 }, "names": [ { "C": "US", "L": "CA", "ST": "San Francisco" } ] } [root@node-x pki]# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server -hostname="localhost,127.0.0.1" grpc-server.json | cfssljson -bare grpc-server ### 一定要将private key转换成pkcs8格式 ### [root@node-x pki]# openssl pkcs8 -topk8 -inform PEM -in grpc-server-key.pem -outform PEM -nocrypt -out grpc-server-key-pkcs8.pem [root@node-x pki]# cat grpc-client.json { "CN": "client", "hosts": [ "localhost", "127.0.0.1" ], "key": { "algo": "ecdsa", "size": 256 } } [root@node-x pki]# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client -hostname="localhost,127.0.0.1" grpc-client.json | cfssljson -bare grpc-client ### 一定要将private key转换成pkcs8格式 ### [root@node-x pki]# openssl pkcs8 -topk8 -inform PEM -in grpc-client-key.pem -outform PEM -nocrypt -out grpc-client-key-pkcs8.pemcat
package com.chenlei.pki;
import com.chenlei.tutorial.GreeterImpl;
import io.grpc.Server;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyServerBuilder;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.logging.Logger;
/**
* @Author: 陈磊
* @Date: 2018/5/31
* @Description:
*/
public class GreeterService {
public static final Logger logger = Logger.getLogger(GreeterService.class.getName());
private Server server;
private final String host;
private final int port;
private final String ca_path;
private final String server_key_path;
private final String server_cert_path;
public GreeterService(String host, int port, String ca_path, String server_key_path, String server_cert_path) {
this.host = host;
this.port = port;
this.ca_path = ca_path;
this.server_key_path = server_key_path;
this.server_cert_path = server_cert_path;
}
private SslContextBuilder getSslContextBuilder () {
SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(new File(server_cert_path), new File(server_key_path));
if (ca_path != null) {
sslContextBuilder.trustManager(new File(ca_path));
// ClientAuth.OPTIONAL表示客户端证书校验是可选的
sslContextBuilder.clientAuth(ClientAuth.OPTIONAL);
}
return GrpcSslContexts.configure(sslContextBuilder, SslProvider.OPENSSL);
}
private void start() throws IOException {
server = NettyServerBuilder.forAddress(new InetSocketAddress(host, port)).addService(new GreeterImpl()).sslContext(getSslContextBuilder().build()).build().start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
GreeterService.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop () {
if (server != null) {
server.shutdown();
}
}
private void blockUntilShutdown () throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
public static void main(String[] args) throws InterruptedException, IOException {
String ca_path = GreeterService.class.getClassLoader().getResource("tls/ca.pem").getPath();
String server_key_path = GreeterService.class.getClassLoader().getResource("tls/grpc-server-key-pkcs8.pem").getPath();
String server_cert_path = GreeterService.class.getClassLoader().getResource("tls/grpc-server.pem").getPath();
GreeterService service = new GreeterService("127.0.0.1", 50051, ca_path, server_key_path, server_cert_path);
service.start();
service.blockUntilShutdown();
}
}
package com.chenlei.pki;
import com.chenlei.tutorial.GreeterGrpc;
import com.chenlei.tutorial.HelloReply;
import com.chenlei.tutorial.HelloRequest;
import io.grpc.ManagedChannel;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.ssl.SslContextBuilder;
import javax.net.ssl.SSLException;
import java.io.File;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* @Author: 陈磊
* @Date: 2018/5/31
* @Description:
*/
public class GreeterClient {
public static final Logger logger = Logger.getLogger(GreeterClient.class.getName());
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
public GreeterClient(ManagedChannel channel) {
this.channel = channel;
this.blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public GreeterClient(String host, int port, SslContextBuilder sslContextBuilder) throws SSLException {
this (NettyChannelBuilder.forAddress(host, port).negotiationType(NegotiationType.TLS).sslContext(sslContextBuilder.build()).build());
}
private static SslContextBuilder getSslContextBuilder (String ca_path, String client_key_path, String client_cert_path) {
SslContextBuilder builder = GrpcSslContexts.forClient();
if (ca_path != null) {
builder.trustManager(new File(ca_path));
}
if (client_cert_path != null && client_key_path != null) {
builder.keyManager(new File(client_cert_path), new File(client_key_path));
}
return builder;
}
public void shutdown () throws InterruptedException {
this.channel.shutdown().awaitTermination(5, TimeUnit.MINUTES);
}
public void greet (String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply reply = blockingStub.sayHello(request);
logger.info("Greeting: " + reply.getMessage());
}
public static void main(String[] args) throws SSLException, InterruptedException {
String ca_path = GreeterService.class.getClassLoader().getResource("tls/ca.pem").getPath();
String client_key_path = GreeterService.class.getClassLoader().getResource("tls/grpc-client-key-pkcs8.pem").getPath();
String client_cert_path = GreeterService.class.getClassLoader().getResource("tls/grpc-client.pem").getPath();
GreeterClient client = new GreeterClient("127.0.0.1", 50051, GreeterClient.getSslContextBuilder(ca_path, client_key_path, client_cert_path));
try {
client.greet("world");
} finally {
client.shutdown();
}
}
}
另外一种生成证书的方式,使用openssl:
LiondeMacBook-Pro:pki lion$ openssl genrsa -out ca-key.pem 2048 LiondeMacBook-Pro:pki lion$ openssl req -new -key ca-key.pem -out ca.csr LiondeMacBook-Pro:pki lion$ openssl x509 -req -days 3650 -in ca.csr -out ca.pem -signkey ca-key.pem LiondeMacBook-Pro:pki lion$ openssl genrsa -out grpc-server-key.pem 2048 LiondeMacBook-Pro:pki lion$ openssl req -new -key grpc-server-key.pem -out grpc-server.csr LiondeMacBook-Pro:pki lion$ openssl x509 -req -days 3650 -in grpc-server.csr -out grpc-server.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial LiondeMacBook-Pro:pki lion$ openssl genrsa -out grpc-client-key.pem 2048 LiondeMacBook-Pro:pki lion$ openssl req -new -key grpc-client-key.pem -out grpc-client.csr LiondeMacBook-Pro:pki lion$ openssl x509 -req -days 3650 -in grpc-client.csr -out grpc-client.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial LiondeMacBook-Pro:pki lion$ openssl pkcs8 -topk8 -inform PEM -in grpc-client-key.pem -outform PEM -nocrypt -out grpc-client-key-pkcs8.pem LiondeMacBook-Pro:pki lion$ openssl pkcs8 -topk8 -inform PEM -in grpc-server-key.pem -outform PEM -nocrypt -out grpc-server-key-pkcs8.pem
目前使用这些证书尚存异常:
java.security.cert.CertificateException: No subject alternative names present
,可能是在生成csr的时候,host字段填错了,暂且记下,待日后查实另行补充~~
3、附件
用到的自签名证书:
4、参考
- https://developers.google.com/protocol-buffers/docs/overview
- https://github.com/grpc/grpc-java/blob/master/SECURITY.md
- https://github.com/grpc/grpc-java/tree/master/examples/src/main
- https://grpc.io/docs/quickstart/java.html
- http://colobu.com/2017/03/16/Protobuf3-language-guide
- http://ginobefunny.com/post/learning_protobuf/
- https://blog.csdn.net/u011518120/article/details/54604615
- http://doc.oschina.net/grpc?t=58008
- https://blog.csdn.net/czk740960212/article/details/80255772
- https://segmentfault.com/a/1190000010040134