使用gRPC进行跨语言服务通信

本文详细介绍了如何使用gRPC实现Java客户端和C++服务端之间的跨语言通信。首先讲解了gRPC的基础,包括基于HTTP/2的通信协议和Protocol Buffers(Protobuf)序列化框架。接着,通过一个实例展示了如何编写.proto文件,以及在Java和C++中如何生成和使用相关代码。在Java端,利用Gradle配置protobuf插件自动生成客户端代码。而在C++端,通过protobuf和gRPC编译器手动生成服务端所需文件,并在CMakeLists.txt中配置依赖库。最后,分别展示了Java客户端和服务端的运行效果,成功实现了跨语言的RPC通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

​     gRPC是基于HTTP/2的通信协议框架(Dubbo是基于tcp的),同时采用了Protocol Buffers(Protobuf) 作为序列化框架。protobuf也是google开源的一款序列化产品。与开发语言无关,和平台无关,具有良好的可扩展性。Protobuf和所有的序列化框架一样,都可以用于数据存储、通讯协议。众所周知,使用一些老牌的rpc框架如Dubbo等很难进行不同语言服务之间的通信(当然Dubbo目前也已经引入了protobuf进行跨语言通信),gRPC作为一款google开源的rpc框架,不仅支持跨语言、跨平台并且性能极高,在云时代,gPRC无疑是一个宠儿。今天就来聊一下gRPC的使用。

示例采用Java做客户端,C++做服务端进行通信,示意图如下:

image-20210913131519199

1.编写proto文件

​     使用gRPC要先编写proto文件(文件名:greet.proto),关于proto文件的介绍可以看下官网

syntax = "proto3";
package greet;
//定义一个service服务,这里在C++和Java中相当于一个类
service Greeting {
  // 定义一个函数,这里在C++和Java中相当于一个方法
  rpc sayHello (GreetRequest) returns (GreetResponse) {}
}
//定义请求的消息实体
message GreetRequest {
  //在C++和Java中这个类型相当于int
  int32 age = 1;
  string name = 2;
}
//定义返回的消息实体
message GreetResponse {
  string result = 1;
}

2.开发Java客户端

使用gradle构建Java项目,build.gradle如下:

plugins {
    id 'java-library'
    id 'com.google.protobuf' version '0.8.17'
}

group 'com.tanky'
version '1.0.0'

repositories {
    mavenLocal()
    mavenCentral()
}
dependencies {
    implementation 'io.grpc:grpc-netty-shaded:1.40.1'
    implementation 'io.grpc:grpc-protobuf:1.40.1'
    implementation 'io.grpc:grpc-stub:1.40.1'
}
protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.17.3"
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.40.1'
        }
    }
    generateProtoTasks.generatedFilesBaseDir="src"
    generateProtoTasks {
        all()*.plugins {
            grpc {
                setOutputSubDir'java'
            }
        }
    }
}

在gradle中引入protobuf的插件,该插件引入以后在执行的时候会下载对应的protobuf的编译器以及将proto文件生成Java代码的插件。将编写好的greet.proto文件放在src/main/poto/下,在插件生成Java代码的时候会自动去改目录下寻找。

image-20210913131916466

执行结束以后会在src/main/java/下生成greet目录,目录中会有两个类分别是Greet和GreetingGrpc,其中Greet就是我们在.proto中定义的message,GreetingRrpc就是我们定义的service。示意图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4rHpAIWr-1631517820117)(https://i.loli.net/2021/09/13/NwjyIRbSphgaKQd.png)]

具体的代码结构在这里就不做过多介绍,我们直接看如何编写Client的代码:

package com.tanky.grpc.client;

import greet.Greet;
import greet.GreetingGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

/**
 * @author tanky
 * Grpc客户端
 */
public class GreetClient {

    public static void main(String[] args) {
        
        //构建channel通道,服务在本机的5000端口启动,这里可以改成自己的ip和端口
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 5000).usePlaintext().build();
        //构建代理对象stub
        GreetingGrpc.GreetingBlockingStub greetingBlockingStub = GreetingGrpc.newBlockingStub(managedChannel);
        //构建请求实体对象
        Greet.GreetRequest greetRequest = Greet.GreetRequest.newBuilder().setAge(18).setName("tanky").build();
        //进行rpc通信,获得返回的实体对象
        Greet.GreetResponse greetResponse = greetingBlockingStub.sayHello(greetRequest);

        System.out.println(greetResponse.getResult());
    }

}

至此,Java Client就完成了。

3.开发C++服务端

​​     C++的服务端开发相对来说复杂一点,我也是好长时间没用C++了又有些生疏,遇到了很多坑。C++不像Java那般可以用插件直接将proto文件生成Java代码,而它必须使用protobuf以及gRPC的编译器,自己将proto文件编译称C++文件。关于protobuf和gRPC的编译器可以在官网下载。编译器可以直接下载可执行文件,也可以下载源码自己进行编译,因为我的电脑是Mac的M1,没有在官网上找到合适的可执行文件,所以自己下载源码进行了编译,并且gRPC官网对于C++的quick start也推荐使用编译源码的方式,具体的使用过程可以看官网Quick start,后续我也将会整理一份放在博客上。

在greet.proto所在目录执行两条shell命令:

#生成 greet.pb.cc、greet.pb.h(请求实体相关类的生成)
protoc --cpp_out=./ greet.proto;
#生成greet.grpc.pb.cc、greet.grpc.pb.h(请求的函数相关的类)
protoc --grpc_out=./ --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` greet.proto;

执行完成后在当前目录会生成greet.pb.cc、greet.pb.h、greet.grpc.pb.cc、greet.grpc.pb.h四个文件,将这两个文件拷贝到自己的C++项目中,我的C++项目是使用Clion编写的,具体如下:

image-20210913150317549

放进去以后C++不像Java般智能,Java可以直接在build.gradle中引入所需要的gRPC的jar包,而C++不行,他所依赖的库必须在本地,并且需要手工引入。C++所依赖的gRPC的库和protobuf的库,都在我的/usr/local/下,这些库在编译gRPC和protobuf的编译器的时候已经生成在/usr/local/下了,此时我们只需要在CMakeLists.txt中定义就可以了。

CMakeLists.txt:

cmake_minimum_required(VERSION 3.19)
#项目名
project(cpuls)
#定义C++标准
set(CMAKE_CXX_STANDARD 11)
#-I 添加头文件目录INCLUDE_DIRECTORIES
include_directories(/usr/local/protobuf/include)
#-L 添加需要链接的库文件目录LINK_DIRECTORIES
link_directories(/usr/local/protobuf/lib)
#-I
include_directories(/usr/local/include)
#-L
link_directories(/usr/local/lib)
add_executable(cpuls Server.cpp greet.pb.cc greet.grpc.pb.cc)
#显示指定链接动态库
target_link_libraries(cpuls protobuf grpc gpr grpc++)

然后编写C++服务端程序代码:

#include <string>
#include <grpcpp/grpcpp.h>
#include "greet.grpc.pb.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using namespace greet;

class MathServiceImplementation final : public Greeting::Service {
    Status sayHello(ServerContext *context, const GreetRequest *request, GreetResponse *reply) override {
        int age = request->age();
        std::string name = request->name();
        std::cout << "年龄为" << age << "姓名为" << name << std::endl;
        reply->set_result("hello world!!!");
        return Status::OK;
    }
};

int main(int argc, char **argv) {
    std::string address("localhost:5000");
    MathServiceImplementation serviceImplementation;
    ServerBuilder builder;
    builder.AddListeningPort(address, grpc::InsecureServerCredentials());
    builder.RegisterService(&serviceImplementation);
    std::unique_ptr<Server> server(builder.BuildAndStart());
    std::cout << "Server listening on port: " << address << std::endl;
    server->Wait();
    return 0;
}

至此,C++服务端代码编写完成。

4.最终效果

启动C++服务端程序终端显示如下:

image-20210913151858037

启动Java客户端程序如下:

image-20210913151953398

服务端返回了hello world!!!。此时再看服务端终端的输出:

image-20210913152105234

服务端输出了姓名和年龄,至此gRPC的跨语言rpc通信演示例子最终效果演示完成。

gRPC(Google Remote Procedure Call)是一个高性能、开源和通用的RPC框架,它能够实现跨语言服务调用。gRPC通过使用Protocol Buffers(Protobuf)作为其接口定义语言(IDL),并结合HTTP/2协议来实现高效的跨语言通信。 具体来说,gRPC实现跨语言服务调用的过程如下: 1. **定义服务**:首先,使用Protobuf定义服务接口。这个接口描述了客户端可以调用的方法以及方法的参数和返回类型。例如: ```protobuf syntax = "proto3"; service MyService { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; } ``` 2. **编译Proto文件**:使用Protobuf编译器(`protoc`)将上述定义的`.proto`文件编译成特定语言的代码。例如,对于C++Java、Python等不同编程语言,会生成相应的客户端和服务器端代码。 3. **实现服务器端逻辑**:根据生成的代码,在目标语言中实现具体的服务逻辑。例如,在Python中,可以继承生成的基类并实现`SayHello`方法: ```python class MyServiceServicer(my_service_pb2_grpc.MyServiceServicer): def SayHello(self, request, context): return my_service_pb2.HelloReply(message='Hello, %s!' % request.name) ``` 4. **启动gRPC服务器**:创建并启动一个gRPC服务器,并将实现的服务绑定到该服务器上。例如,在Python中: ```python server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) my_service_pb2_grpc.add_MyServiceServicer_to_server(MyServiceServicer(), server) server.add_insecure_port('[::]:50051') server.start() server.wait_for_termination() ``` 5. **实现客户端逻辑**:同样地,根据生成的客户端代码,在目标语言中编写客户端逻辑来调用远程服务。例如,在Python中: ```python with grpc.insecure_channel('localhost:50051') as channel: stub = my_service_pb2_grpc.MyServiceStub(channel) response = stub.SayHello(my_service_pb2.HelloRequest(name='world')) print("Client received: " + response.message) ``` 6. **运行客户端与服务器**:分别运行客户端和服务器程序,客户端将能够成功调用远程服务并获取响应。 通过以上步骤,gRPC能够实现不同编程语言之间的高效通信服务调用。这种跨语言的能力使得开发者可以选择最适合自己项目需求的语言来构建系统的各个部分,同时保持系统的互操作性和一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值