gRPC是基于HTTP/2的通信协议框架(Dubbo是基于tcp的),同时采用了Protocol Buffers(Protobuf) 作为序列化框架。protobuf也是google开源的一款序列化产品。与开发语言无关,和平台无关,具有良好的可扩展性。Protobuf和所有的序列化框架一样,都可以用于数据存储、通讯协议。众所周知,使用一些老牌的rpc框架如Dubbo等很难进行不同语言服务之间的通信(当然Dubbo目前也已经引入了protobuf进行跨语言通信),gRPC作为一款google开源的rpc框架,不仅支持跨语言、跨平台并且性能极高,在云时代,gPRC无疑是一个宠儿。今天就来聊一下gRPC的使用。
示例采用Java做客户端,C++做服务端进行通信,示意图如下:
![image-20210913131519199](https://i.loli.net/2021/09/13/FYLqv5Q4nxa3zMW.png)
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](https://i.loli.net/2021/09/13/9J5XWFw1GV3Aigb.png)
执行结束以后会在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](https://i.loli.net/2021/09/13/1RjUOsDFEuHYlI9.png)
放进去以后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](https://i.loli.net/2021/09/13/Aq2kyBTKH65Q7eR.png)
启动Java客户端程序如下:
![image-20210913151953398](https://i.loli.net/2021/09/13/zpk14EGsZ3aDmyO.png)
服务端返回了hello world!!!。此时再看服务端终端的输出:
![image-20210913152105234](https://i.loli.net/2021/09/13/kvVil37jUxXSHuh.png)
服务端输出了姓名和年龄,至此gRPC的跨语言rpc通信演示例子最终效果演示完成。