通过rpc的方式实现java调用python,这种方式将不局限于java调用python,同时python也可以调用java。
而且最大程度的实现了解耦,程序间不兼容的问题将会不存在。
本教程将基于以下环境,构建rpc服务。
java 1.8.0_191
Python 3.8.3
gradle 5.6.3
grpc 1.30.2
IntelliJ IDEA 2020.1 (Ultimate Edition)
PyCharm 2020.1 (Professional Edition)
gRPC参考资料
官方文档
gRPC 官方文档中文版V1.0
java
.proto 文件中定义服务
推荐安装插件:Protocol Buffer Editor
proto\demo_service.proto
syntax = "proto3";
option java_multiple_files = true;
option java_package = "demo.server";
option java_outer_classname = "DemoServiceProto";
package demo.server;
service DemoService {
rpc SayHello (DemoRequest) returns (DemoReply) {}
}
message DemoRequest {
string name = 1;
}
message DemoReply {
string message = 1;
}
语法含义具体参考官网:proto3 语言指南(需科学上网)
上面示例中出现的语法解释:
syntax:指定了使用的proto3语法,如果不指定,默认使用proto2。这个语句必须在.proto文件的非空非注释的第一行。
java_multiple_files(文件选项):指定在proto文件中定义的所有消息、枚举和服务在生成java类的时候都会生成对应的java类文件,而不是以内部类的形式出现。
java_package(文件选项):用于生成Java类的包。如果java_package在.proto文件中未指定任何显式选项,则默认情况下将使用proto定义的包(在.proto文件中使用“ package”关键字指定)。但是,proto定义的包通常不能作为Java包,因为proto定义的包不应以反向域名开头。如果未生成Java代码,则此选项无效。
java_outer_classname(文件选项):您要生成的最外层Java类的类名(以及文件名)。如果java_outer_classname在.proto文件中未指定任何显式名称,则通过将.proto文件名转换为驼峰大小写来构造类名(因此demo_service.proto变为DemoService.java)。如果未生成Java代码,则此选项无效。
package:可以为.proto文件指定包名,以防止协议消息类型之间的命名冲突。
service:如果要将消息类型与RPC(远程过程调用)系统一起使用,则可以在.proto文件中定义RPC服务接口。
定义消息类型
DemoRequest消息定义指定了一个字段(名称/值对),每个字段都有一个名称和一个类型。
指定字段类型
示例中方法名为SayHello,方法参数使用定义的消息类型:DemoRequest,返回类型使用定义的消息类型:DemoReply。
分配字段编号
消息定义中的每个字段都有一个唯一的编号。这些字段号用于标识消息二进制格式的字段,一旦使用了消息类型,就不应更改这些字段号。请注意,范围为1到15的字段编号需要一个字节进行编码,范围16到2047的字段号占用两个字节。因此,您应该为经常出现的消息元素保留数字1到15。请记住为将来可能添加的频繁出现的元素留出一些空间。
可以指定的最小字段数是1,最大字段数是2^29 - 1
或 536,870,911。您也不能使用19000到19999这两个数字(FieldDescriptor::kFirstReservedNumber通过FieldDescriptor::kLastReservedNumber),因为它们是为协议缓冲区实现保留的——如果您在.proto中使用这些保留数字之一,协议缓冲区编译器将会显示编译错误。
gradle配置信息
添加依赖
implementation 'io.grpc:grpc-netty-shaded:1.30.2'
implementation 'io.grpc:grpc-protobuf:1.30.2'
implementation 'io.grpc:grpc-stub:1.30.2'
与Gradle构建系统集成的基于protobuf基础代码的源码自动生成工具,可以使用 protobuf-gradle-plugin:
plugins {
id "com.google.protobuf" version "0.8.12"
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.12.0"
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.30.2'
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
在项目根目录执行gradle generateProto
即可生成proto文件对应的java源代码,生成路径如下:
定义服务DemoService相关代码: build\generated\source\proto\main\grpc
定义服务方法参数和返回值相关代码:build\generated\source\proto\main\java
将生成的代码告知IDE工具进行管理
sourceSets {
main {
java {
srcDirs 'build/generated/source/proto/main/grpc'
srcDirs 'build/generated/source/proto/main/java'
}
}
}
服务端实现
public class DemoServerImpl extends DemoServiceGrpc.DemoServiceImplBase {
@Override
public void sayHello(DemoRequest request, StreamObserver<DemoReply> responseObserver) {
DemoReply reply = DemoReply.newBuilder().setMessage("Hello " + request.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
服务启动类实现
public class Main {
private Server server;
private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new DemoServerImpl())
.build()
.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
try {
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 Main main = new Main();
main.start();
main.blockUntilShutdown();
}
}
客户端实现
public class DemoClient {
public static void main(String[] args) throws Exception {
String name = "world!!!!!!!!!!!!!!!!!!!!!!!";
String target = "localhost:50051";
ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
.usePlaintext()
.build();
DemoServiceGrpc.DemoServiceBlockingStub blockingStub = DemoServiceGrpc.newBlockingStub(channel);
DemoRequest request = DemoRequest.newBuilder().setName(name).build();
try {
DemoReply reply = blockingStub.sayHello(request);
System.out.println("返回内容 === " + reply.getMessage());
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
}
测试
启动服务启动类
启动客户端
python
.proto 文件中定义服务
与java步骤相同
环境配置
安装依赖的模块
pip install grpcio
pip install grpcio-tools
在项目根目录执行以下命令生成服务所需源代码
python -m grpc_tools.protoc -I ./proto --python_out=. --grpc_python_out=. ./proto/demo_service.proto
服务段实现
import demo_service_pb2
import demo_service_pb2_grpc
class DemoService(demo_service_pb2_grpc.DemoServiceServicer):
def SayHello(self, request, context):
return demo_service_pb2.HelloReply(message='hello {msg}'.format(msg=request.name))
服务启动脚本实现
from concurrent import futures
import grpc
import demo_service_pb2_grpc
from demo.demo_service_impl import DemoService
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
demo_service_pb2_grpc.add_DemoServiceServicer_to_server(
DemoService(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
if __name__ == '__main__':
serve()
客户端实现
from __future__ import print_function
import grpc
import demo_service_pb2
import demo_service_pb2_grpc
def run():
with grpc.insecure_channel('localhost:50051') as channel:
stub = demo_service_pb2_grpc.DemoServiceStub(channel)
response = stub.SayHello(demo_service_pb2.DemoRequest(name="word !!!!!!!!!!!!!!!!!!!!!"))
print("Received message %s" %response.message)
if __name__ == '__main__':
run()
测试
启动服务启动类
启动客户端
总结
使用上面示例代码的java客户端可以访问python的服务端,反之亦然。
GitHub完整源代码
java示例
python示例
评论