初步了解 Protobuf 和 gRpc
Protocol Buffers
Protocol Buffers(又称protobuf)是谷歌的语言无关、平台无关、可扩展的机制,用于序列化结构化数据。您可以在protobuf的文档中了解更多关于它的信息。
ProtoBuf 的定义
ProtoBuf是将类的定义使用.protobuf文件进行描述。
//语言版本
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
package helloworld;
//数据体User 属性username
message User{
string username = 1;
}
通过protoc可以将.proto文件编译成需要的语言文件。本文以java语言示例。
protoc --proto_path=src --java_out=build/gen src/foo.proto
详细用法可以查看protoc的使用。
gRpc
在 gRPC 中,客户端应用程序可以像本地对象一样直接调用不同机器上的服务器应用程序上的方法,从而使您可以更轻松地创建分布式应用程序和服务。与许多 RPC 系统一样,gRPC 基于定义服务的思想,指定可以远程调用的方法及其参数和返回类型。在服务器端,服务器实现这个接口并运行一个gRPC服务器来处理客户端调用。在客户端,客户端有一个存根(在某些语言中简称为客户端),它提供与服务器相同的方法。(类似Android AIDL使用方式)。
gRPC 客户端和服务器可以在各种环境中运行并相互通信(从 Google 内部的服务器到您自己的桌面),并且可以用 gRPC 支持的任何语言编写。例如,您可以使用 Java 轻松创建 gRPC 服务器,并使用 Go、Python 或 Ruby 编写客户端。
默认情况下,gRPC 使用Protocol Buffers,Google 成熟的开源机制,用于序列化结构化数据(尽管它可以与 JSON 等其他数据格式一起使用)。
【以上来自grpc简介】
Grpc 的定义
grpc 也定义在.proto文件,如下:
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the name and user.
message HelloRequest {
string name = 1;
User user = 2;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
grpc 的编译仍然需要编译.proto文件。所以离不开protoc, 还需要另外的插件配合编译。例如:
protoc --plugin=protoc-gen-grpc-java \
--grpc-java_out="$OUTPUT_FILE" --proto_path="$DIR_OF_PROTO_FILE" "$PROTO_FILE"
Protobuf 插件
使用命令行对于大部分人来说是比较不方便的,我们也可以使用Gradle插件来完成.proto的编译。下面分享一下完整的build.gradle文件。
plugins {
// Provide convenience executables for trying out the examples.
id 'application'
id 'com.google.protobuf' version '0.9.4'
// Generate IntelliJ IDEA's .idea & .iml project files
id 'idea'
}
repositories {
maven { // The google mirror is less flaky than mavenCentral()
url "https://maven-central.storage-download.googleapis.com/maven2/"
}
mavenCentral()
mavenLocal()
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you
// are looking at a tagged version of the example and not "master"!
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
def grpcVersion = '1.61.0' // CURRENT_GRPC_VERSION
def protobufVersion = '3.25.1'
def protocVersion = protobufVersion
//https://github.com/protocolbuffers/protobuf
//https://github.com/grpc/grpc-java
dependencies {
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
implementation "io.grpc:grpc-services:${grpcVersion}"
implementation "io.grpc:grpc-stub:${grpcVersion}"
compileOnly "org.apache.tomcat:annotations-api:6.0.53"
// examples/advanced need this for JsonFormat
implementation "com.google.protobuf:protobuf-java-util:${protobufVersion}"
runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}"
testImplementation "io.grpc:grpc-testing:${grpcVersion}"
testImplementation "io.grpc:grpc-inprocess:${grpcVersion}"
testImplementation "junit:junit:4.13.2"
testImplementation "org.mockito:mockito-core:4.4.0"
}
//https://github.com/google/protobuf-gradle-plugin
protobuf {
protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
plugins {
grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
}
generateProtoTasks {
all()*.plugins { grpc {} }
}
}
// Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code.
sourceSets {
main {
java {
srcDirs 'build/generated/source/proto/main/grpc'
srcDirs 'build/generated/source/proto/main/java'
}
}
}
通过build项目或者执行generateProto的task就可以生成java文件。
上面的.proto文件生成目录如下:
下面提供java中的使用,所有代码只有server和client两个文件。如下:
/*
* Copyright 2015 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.examples.helloworld;
import io.grpc.Grpc;
import io.grpc.InsecureServerCredentials;
import io.grpc.Server;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* Server that manages startup/shutdown of a {@code Greeter} server.
*/
public class HelloWorldServer {
private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
private Server server;
private void start() throws IOException {
/* The port on which the server should run */
int port = 50051;
server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
.addService(new GreeterImpl())
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
try {
HelloWorldServer.this.stop();
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
System.err.println("*** server shut down");
}
});
}
private void stop() throws InterruptedException {
if (server != null) {
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
}
}
/**
* Await termination on the main thread since the grpc library uses daemon threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/**
* Main launches the server from the command line.
*/
public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldServer server = new HelloWorldServer();
server.start();
server.blockUntilShutdown();
}
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
System.out.println("sayHello:"+req.getUser().getUsername()+req.getName());
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " +req.getUser().getUsername()+req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}
/*
* Copyright 2015 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.examples.helloworld;
import io.grpc.Channel;
import io.grpc.Grpc;
import io.grpc.InsecureChannelCredentials;
import io.grpc.ManagedChannel;
import io.grpc.StatusRuntimeException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A simple client that requests a greeting from the {@link HelloWorldServer}.
*/
public class HelloWorldClient {
private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
private final GreeterGrpc.GreeterBlockingStub blockingStub;
/**
* Construct client for accessing HelloWorld server using the existing channel.
*/
public HelloWorldClient(Channel channel) {
// 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to
// shut it down.
// Passing Channels to code makes code easier to test and makes it easier to reuse Channels.
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
/**
* Say hello to server.
*/
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).setUser(User.newBuilder().setUsername("test ").build()).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}
/**
* Greet server. If provided, the first element of {@code args} is the name to use in the
* greeting. The second argument is the target server.
*/
public static void main(String[] args) throws Exception {
String user = "world";
// Access a service running on the local machine on port 50051
String target = "localhost:50051";
// Allow passing in the user and target strings as command line arguments
if (args.length > 0) {
if ("--help".equals(args[0])) {
System.err.println("Usage: [name [target]]");
System.err.println("");
System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
System.err.println(" target The server to connect to. Defaults to " + target);
System.exit(1);
}
user = args[0];
}
if (args.length > 1) {
target = args[1];
}
// Create a communication channel to the server, known as a Channel. Channels are thread-safe
// and reusable. It is common to create channels at the beginning of your application and reuse
// them until the application shuts down.
//
// For the example we use plaintext insecure credentials to avoid needing TLS certificates. To
// use TLS, use TlsChannelCredentials instead.
ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create())
.build();
try {
HelloWorldClient client = new HelloWorldClient(channel);
client.greet(user);
} finally {
// ManagedChannels use resources like threads and TCP connections. To prevent leaking these
// resources the channel should be shut down when it will no longer be used. If it may be used
// again leave it running.
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
}