当前位置 : 主页 > 大数据 > 区块链 >

Protobuf&gRPC简介

来源:互联网 收集:自由互联 发布时间:2021-06-22
Protobuf gRPC简介 1、Protobuf 1.1、概念 Protobuf是Google protocol buffer的简称,是一种语言中立、平台无关、易于扩展的结构化数据序列化技术,可用于数据传输、存储等领域。 与Protoful类似的序

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

  • 语法参考:官网、中文
  • 事例文件:/Users/lion/IdeaProjects/X-Project/protobuf/addressbook.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
  • http://www.voidcn.com/article/p-vgxfooag-e.html
网友评论