在 gRPC 中,客户端应用程序可以直接调用另一台计算机上的服务器应用程序上的方法,就好像它是本地对象一样,从而使您可以更轻松地创建分布式应用程序和服务。与许多 RPC 系统一样,gRPC 基于定义服务的思想,指定可以使用其参数和返回类型远程调用的方法。在服务器端,服务器实现此接口并运行 gRPC 服务器来处理客户端调用。在客户端,客户端有一个存根,该存根提供与服务器相同的方法。
HTTP/2 提供了连接多路复用、双向流、服务器推送、请求优先级、首部压缩等机制。可以节省带宽、降低TCP链接次数、节省CPU,帮助移动设备延长电池寿命等。gRPC 的协议设计上使用了HTTP2 现有的语义,请求和响应的数据使用HTTP Body 发送,其他的控制信息则用Header 表示。
gRPC使用ProtoBuf来定义服务,ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON、hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。压缩和传输效率高,语法简单,表达力强。
gRPC支持多种语言(C, C++, Python, PHP, Nodejs, C#, Objective-C、Golang、Java),并能够基于语言自动生成客户端和服务端功能库。目前已提供了C版本grpc、Java版本grpc-java 和 Go版本grpc-go,其它语言的版本正在积极开发中,其中,grpc支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#等语言,grpc-java已经支持Android开发。
优点
缺点:
protobuf 是 Google 公司内部的混合语言数据标准,默认情况下,grpc使用的就是protobuf。你可以理解Protocol Buffers是一种更加灵活、高效的数据格式,与XML、JSON类似,在一些高性能且对响应速度有要求的数据传输场景非常适用。
目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API(即时通讯网注:Protobuf官方工程主页上显示的已支持的开发语言多达10种,分别有:C++、Java、Python、Objective-C、C#、JavaNano、JavaScript、Ruby、Go、PHP,基本上主流的语言都已支持,详见工程主页:https://github.com/52im/protobuf)。
在RPC框架中的作用:
在后缀为.proto文件中定义数据结构(被远程调用服务的接口文档)
//使用的版本号
syntax = "proto3";
option go_package = "../proto;client";
option java_multiple_files = true;
option java_package="com.example.grpcexample.helloworld";
//请求
message Request {
double num1 = 1;
double num2 = 2;
OperateType opType = 3;
}
//枚举类
enum OperateType {
Addition = 0;
Division = 1;
Multiplication = 2;
Subtraction = 3;
}
//响应
message Response {
double result = 1;
}
//定义服务
service Operate {
//一元RPC
rpc Calculate (Request) returns (Response);
//服务器流式 RPC
rpc Calculate (Request) returns (stream Response);
//客户端流式RPC
rpc Calculate (stream Request) returns (Response);
//双向流式RPC
rpc Calculate (stream Request) returns (Response);
}
四种服务类型介绍(消息传输)
第二步,根据服务端(服务提供者)和客户端(服务调用者)语言类型,生成相应的代码,然后再实现/调用方法。
编写protobuf文件
helloworld.proto
syntax = "proto3";
option go_package = "../proto;helloworld";
option java_multiple_files = true;
option java_package="com.example.grpc_study.proto";
message Request{
string source=1;
string language=2;
}
message Response {
string source=1;
string language=2;
}
service HelloWorld {
rpc TestRpc(Request) returns (Response);
}
编写Go服务端
安装grpc:
go get -u google.golang.org/grpc
安装编译proto文件的插件
go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/protoc-gen-go
在proto文件目录中执行命令,生成代码
protoc -I . --go_out=plugins=grpc:. helloworld.proto
server.go
package main
import (
"context"
"fmt"
"net"
"google.golang.org/grpc"
"grpc_study/proto"
"log"
)
type server struct{
}
func (server) TestRpc(ctx context.Context,req *helloworld.Request)(*helloworld.Response,error){
source:=req.Source
language:=req.Language
fmt.Printf("接收到来自%s%s的请求",language,source)
return &helloworld.Response{Source:"服务端",Language:"Go"},nil
}
func main(){
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
helloworld.RegisterHelloWorldServer(s,&server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
启动服务端:go run server.go
编写Java客户端
创建SpringBoot项目
配置maven
<dependency>
<groupId>net.devhgroupId>
<artifactId>grpc-server-spring-boot-starterartifactId>
<version>2.13.1.RELEASEversion>
dependency>
<plugin>
<groupId>org.xolstice.maven.pluginsgroupId>
<artifactId>protobuf-maven-pluginartifactId>
<version>0.5.1version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}protocArtifact>
<pluginId>grpc-javapluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.20.0:exe:${os.detected.classifier}pluginArtifact>
<outputDirectory>${project.build.sourceDirectory}outputDirectory>
<clearOutputDirectory>falseclearOutputDirectory>
configuration>
<executions>
<execution>
<goals>
<goal>compilegoal>
<goal>compile-customgoal>
goals>
execution>
executions>
plugin>
在java的同级目录创建proto文件夹,并将其标记为Sources Root
在proto目录中创建helloworld.proto,内容go服务端内容一样
执行:mvn clean install,生成代码
创建Client类
public class Client {
private final ManagedChannel channel;
private final HelloWorldGrpc.HelloWorldBlockingStub blockingStub;
private Client(ManagedChannel channel) {
this.channel = channel;
blockingStub = HelloWorldGrpc.newBlockingStub(channel);
}
public Client(String host, int port) {
this(ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build());
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public static void main(String[] args) {
try {
Client service = new Client("localhost", 9090);
Request request=Request.newBuilder().setLanguage("Java").setSource("客户端").build();
Response response = service.blockingStub.testRpc(request);
System.out.println("服务调用成功,接收到来自"+response.getLanguage()+response.getSource()+"的响应");
service.shutdown();
} catch (Exception e) {
System.out.println(e);
}
}
}
启动服务端,再启动客户端
编写服务端
Server.java
@GrpcService
public class Server extends HelloWorldGrpc.HelloWorldImplBase {
@Override
public void testRpc(Request request, StreamObserver<Response> streamObserver){
System.out.println("接收到来自"+request.getLanguage()+request.getSource()+"的请求");
streamObserver.onNext(Response.newBuilder().setLanguage("Java").setSource("服务端").build());
streamObserver.onCompleted();
}
}
启动SpringBoot应用,grpc服务默认启动的是9090端口
编写客户端
client.go
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"grpc_study/proto"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithInsecure())
if err != nil {
fmt.Println("grpc Dial error", err)
return
}
client := helloworld.NewHelloWorldClient(conn)
rep, err := client.TestRpc(context.TODO(), &helloworld.Request{Source:"客户端",Language:"Go"})
if err == nil {
fmt.Printf("服务调用成功,收到来自:%s%s的响应", rep.Language,rep.Source)
}
}
启动服务端,再启动客户端
//使用的版本号
syntax = "proto3";
//生成go代码的位置,及包名
option go_package = "../proto;helloworld";
//生成java代码,是否选择多文件
option java_multiple_files = true;
//生成java代码的路径
option java_package="com.example.grpc_study.proto";
//消息体,1,2为字段标签,用于序列化
message Request{
string source=1;
string language=2;
}
message Response {
string source=1;
string language=2;
}
//服务名
service HelloWorld {
//TestRpc服务端提供的方法名
rpc TestRpc(Request) returns (Response);
}
用来引入其他文件的内容
格式:import “/xxx路径”,路径是从protoc这个命令的当前目录开始算起
在protobuf中用来定义消息类型的关键字
格式
message 参数名称{
修饰符 数据类型 字段名=标签;
.
.
.
}
修饰符:
数据类型:

默认值
string类型,默认值是空字符串,注意不是nullbytes类型,默认值是空bytesbool类型,默认值是false数字类型,默认值是0枚举类型,默认值是第一个枚举值,即0repeated修饰的属性,默认值是空(在相对应的编程语言中通常是一个空的list).Any类型,类似于Java的Object
如果使用any类型,需要导入:google/protobuf/any.proto
import "google/protobuf/any.proto";
message Response {
string msg = 1;
int32 code = 2;
google.protobuf.Any data = 3; //可以理解成Object
repeated google.protobuf.Any datas = 4; //可以理解成泛型 List datas;
}
标签:
用法
//普通的消息类型
message Person {
string name=1;
}
//嵌套消息,无层级限制
message Person {
string name=1;
message Man{
string name =1;
}
repeated Man man=2;
}
message Student{
Person.Man man=1;
}
格式
enum 标识符 {
字段名=0;
字段名1=1;
}
格式
service 服务名 {
rpc 方法名(请求消息) returns (响应消息);
rpc 方法名(stream 请求消息) returns (响应消息);
rpc 方法名(请求消息) returns (stream 响应消息);
rpc 方法名(stream 请求消息) returns (stream 响应消息);
}