RPC框架之gRPC简单使用
前言
gRPC是一个谷歌推出的高性能的RPC框架,遵从server/client模型,可以使client调用server的接口就像调用本地方法一样简单,gRPC使用ProtoBuf来定义数据类型和接口,相比RestAPI,传输数据更少,性能更高。
下载proto编译器
不仅需要下载protoc编译器,使用gRPC还需要下载grpc-protoc插件(Java需要),这里是windows版本。
定义proto文件
复制syntax = "proto3";
package tutorial;
option java_package = "com.imooc.sourcecode.java.google.grpc.test1";
option java_outer_classname = "PersonProto";
option java_generic_services = true;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
例子来源于官方文档
service 用来定义服务接口,可以看做Java中的接口。
使用Java编写服务器端和客户端代码
添加maven依赖
复制<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.45.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.45.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.45.1</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
根据proto文件创建Java数据类型和接口代码
有两种方式,一种是使用命令行,另一种是使用maven插件
命令行
复制protoc --proto_path=$SRC_DIR --plugin=$P_NAME=$P_DIR --grpc-java_out=$DST_DIR --java_out=$DST_DIR person.proto
--proto_path(也可以简写为-I)表示查找proto文件的路径,
--plugin表示要添加的执行插件,格式为name(插件名称)=dir(路径),
--grpc-java_out表示生成服务接口的路径,
--java_out表示生成数据类型的路径,
实际命令为
复制.\protoc.exe --proto_path=D:\java\code_resp\github_resp\source_code\src\main\java\com\imooc\sourcecode\java\google\grpc\test1 --plugin=protoc-gen-grpc-java=D:\java\software\protoc-3.20.0-win64\bin\protoc-gen-grpc-java-1.4.0-windows-x86_64.exe --grpc-java_out=D:\java\code_resp\github_resp\source_code\src\main\java --java_out=D:\java\code_resp\github_resp\source_code\src\main\java person.proto
--grpc-java_out和--java_out值不要包含具体的包路径,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>
<clearOutputDirectory>false</clearOutputDirectory>
<protoSourceRoot>${basedir}/src/main/java/com/imooc/sourcecode/java/google/grpc/test1</protoSourceRoot>
<outputDirectory>${basedir}/src/main/java</outputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
参考官方文档,自定义了三个属性,
clearOutputDirectory表示代码生成前是否清除输出目录,默认为true。
protoSourceRoot属性表示查找proto文件的路径,默认路径为${basedir}/src/main/proto。
outputDirectory属性表示生成文件的路径,不需要包含具体的包路径,proto文件中已经配置了,默认路径为${project.build.directory}/generated-sources/protobuf/java。
插件的更多属性可以查看Maven Protocol Buffers Plugin。

protobuf:compile任务会生成HelloProto.java文件,protobuf:compile-custom任务会生成GreeterGrpc.java文件,插件底层也是通过命令行方式来实现的。
服务器端代码
复制import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloReply;
import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloRequest;
import io.grpc.Server;
import io.grpc.ServerBuilder;
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 = ServerBuilder.forPort(port)
.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) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@Override
public void sayHelloAgain(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello again " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}
核心在于GreeterImpl实现类,处理客户端的请求。
客户端代码
复制import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloReply;
import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloRequest;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
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).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.
ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
// Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
// needing certificates.
.usePlaintext()
.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);
}
}
}
客户端和服务器端代码都来自grpc-java。
编写Python服务器端和客户端代码
安装依赖
复制pip install grpcio
pip install grpcio-tools
PyCharm中可以这样安装

根据proto文件创建Python数据类型和接口代码
复制python -m grpc_tools.protoc -I=$SRC_DIR --python_out=$DST_DIR --grpc_python_out=$DST_DIR person.proto
-m表示以脚本的方式运行python模块。
-I表示查找proto文件的路径,
--grpc_python_out表示生成服务接口代码的路径,
--python_out表示生成数据类型代码的路径,
实际命令为
复制.\python.exe -m grpc_tools.protoc -I=D:\java\code_resp\PycharmProjects\test_protobuf\grpc --python_out=D:\java\code_resp\PycharmProjects\test_protobuf\grpc --grpc_python_out=D:\java\code_resp\PycharmProjects\test_protobuf\grpc person.proto
服务器端代码
复制from concurrent import futures
import logging
import grpc
import person_pb2
import person_pb2_grpc
class Greeter(person_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return person_pb2.HelloReply(message='Hello, %s!' % request.name)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
person_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
if __name__ == '__main__':
logging.basicConfig()
serve()
核心在于Greeter类,处理来自客户端的请求。
客户端代码
复制from __future__ import print_function
import logging
import grpc
import person_pb2
import person_pb2_grpc
def run():
# NOTE(gRPC Python Team): .close() is possible on a channel and should be
# used in circumstances in which the with statement does not fit the needs
# of the code.
with grpc.insecure_channel('localhost:50051') as channel:
stub = person_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(person_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)
if __name__ == '__main__':
logging.basicConfig()
run()
客户端和服务器端代码都来自grpc。
总结
通过上述测试,我们可以发现,gRPC可以跨语言通信,如Java作为服务器端,Python作为客户端。更多gRPC相关用法,请查看官方文档。
参考
gRPC-官方文档
如何给老婆解释什么是RPC
使用maven插件生成grpc所需要的Java代码
java protobuf 生成grpc 代码
gRPC详解
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
2021-04-12 java中StreamAPI的Collector原理分析