Java 如何调用 Python(二)

用户头像
wjchenge
关注
发布于: 2020 年 07 月 21 日
Java如何调用Python(二)

通过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服务接口。



  1. 定义消息类型

DemoRequest消息定义指定了一个字段(名称/值对),每个字段都有一个名称和一个类型。



  1. 指定字段类型

示例中方法名为SayHello,方法参数使用定义的消息类型:DemoRequest,返回类型使用定义的消息类型:DemoReply。



  1. 分配字段编号

消息定义中的每个字段都有一个唯一的编号。这些字段号用于标识消息二进制格式的字段,一旦使用了消息类型,就不应更改这些字段号。请注意,范围为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 {
/* The port on which the server should run */
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new DemoServerImpl())
.build()
.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// 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 {
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);
}
}
}



测试

  1. 启动服务启动类

  2. 启动客户端



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()



测试

  1. 启动服务启动类

  2. 启动客户端



总结



使用上面示例代码的java客户端可以访问python的服务端,反之亦然。



GitHub完整源代码



java示例

python示例

发布于: 2020 年 07 月 21 日 阅读数: 23
用户头像

wjchenge

关注

还未添加个人签名 2018.07.27 加入

还未添加个人简介

评论

发布
暂无评论
Java如何调用Python(二)