一.gRPC简介 gRPC和RPC区别
在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.
gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。

二.搭建本地环境
安装go语言并配置环境变量
2.1配置Protoc可执行文件
下载地址https://github.com/protocolbuffers/protobuf/releases/download/v3.6.0/protoc-3.6.0-win32.zip
把这么文件里面的bin里面的protoc.exe 复制到GOPATH/bin下,GOPATH/bin加入环境变量。也可以放到C:\windows\System32文件夹下。
2.2获取protobuf的编译器插件 protoc-gen-go
go get -u github.com/golang/protobuf/protoc-gen-go |
2.3 安装gRPC
a.能够访问谷歌可以直接运行
go get -u google.golang.org/grpc |
b. 从github上克隆grpc的依赖库到GOPATH目录
编译安装gRPC
git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto cd $GOPATH/src/ go install google.golang.org/grpc |
2.4安装IDE相应的插件
file->Settings->Plugins->Browse repositories->输入protobuf support->install

三.Protobuf
3.1什么是Protobuf
gRPC 默认使用 protocol buffers,这是 Google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)。
Protobuf全称是Google Protocol Buffer,是一种高效轻便的结构化数据存储方式,可用于(数据)通信协议、数据存储等。
也可以理解为结构化数据的序列化方法,可简单类比为XML(这里主要是指在数据通信和数据存储这些应用场景中序列化方面的类比),其具有以下特点:
- 语言无关,平台无关
Protobuf支持Java, C++, Python,Go等多种语言,支持多个平台。 - 高效
比XML更小(3~10倍),更快(20 ~ 100倍),更为简单。 - 扩展性,兼容性好
可以更新数据结构,而不影响和破坏原有的旧程序。
3.2定义一个.proto文件
proto3代表该文件采用的规范
HelloRequest是定义的结构体,用于传递数据
syntax = "proto3";
package hello;
service Hello{
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
|
3.3将.proto文件转换为go文件
.proto文件无法直接被变成语言直接调用,需要转换为相应语言的文件供编程语言调用
protoc --go_out=plugins=grpc:. hello.proto |
运行完成后可以生成相应的文件test.pb.go,该文件是可以被go语言直接使用的。
四.gRPC使用
4.1创建server端
package main
import (
"context"
"log"
"net"
pb "grpcpro/protoc"
"google.golang.org/grpc"
)
const (
addr = "127.0.0.1:50052"
)
type server struct{}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.Name)
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func main() {
s := grpc.NewServer()
pb.RegisterHelloServer(s, &server{})
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
|
4.2创建client端
package main
import (
"context"
"log"
"os"
"time"
pb "grpcpro/protoc"
"google.golang.org/grpc"
)
const (
address = "127.0.0.1:50052"
defaultName = "world"
)
func main() {
// grpc.Dial负责和GRPC服务建立链接
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// 然后NewGreeterClient函数基于已经建立的链接构造GreeterClient对象
// 返回的client其实是一个GreeterClient接口对象,通过接口定义的方法就可以调用服务端对应的GRPC服务提供的方法。
c := pb.NewHelloClient(conn)
// client第一个参数作为name的输入
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
//5秒超时
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Hello: %s", r.Message)
}
|
编译执行文件后可获得如下输出
go build ./server ./client hihi |
./client hihi
server提示:2019/11/29 14:36:51 Received: hihi
client提示:2019/11/29 14:37:02 Hello: Hello hihi
4.3gRPC证书认证使用
Server端改造
使用http的http.ListenAndServeTLS代替net.Listen
package main
import (
"context"
"log"
"net/http"
pb "grpcpro/protoc"
"google.golang.org/grpc"
)
const (
addr = "127.0.0.1:50052"
)
type server struct{}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.Name)
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func main() {
s := grpc.NewServer()
// var size = 1024 * 1024 * 1024 //1024M
// s := grpc.NewServer(grpc.MaxMsgSize(size), grpc.MaxRecvMsgSize(size), grpc.MaxSendMsgSize(size))
pb.RegisterHelloServer(s, &server{})
if err:=http.ListenAndServeTLS(addr, "../cert/server.crt", "../cert/server.key",
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s.ServeHTTP(w, r) // GRPC Server
return
}),
);err!=nil{
panic(err)
}
}
|
Client端改造
client应该使用生成的client证书,在客户端就基于CA证书对服务器进行证书验证
package main
import (
"context"
"log"
"os"
"time"
"crypto/x509"
"crypto/tls"
"io/ioutil"
"google.golang.org/grpc/credentials"
pb "grpcpro/protoc"
"google.golang.org/grpc"
)
const (
address = "127.0.0.1:50052"
defaultName = "world"
)
func main() {
certificate, err := tls.LoadX509KeyPair("../cert/client.crt", "../cert/client.key")
if err != nil {
log.Fatal(err)
}
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("../cert/ca.crt")
if err != nil {
log.Fatal(err)
}
if ok := certPool.AppendCertsFromPEM(ca); !ok {
log.Fatal("failed to append ca certs")
}
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{certificate},
ServerName: "grpcpro1", // NOTE: this is required!
RootCAs: certPool,
})
// grpc.Dial负责和GRPC服务建立链接
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// 然后NewGreeterClient函数基于已经建立的链接构造GreeterClient对象
// 返回的client其实是一个GreeterClient接口对象,通过接口定义的方法就可以调用服务端对应的GRPC服务提供的方法。
c := pb.NewHelloClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
//5秒超时
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Hello: %s", r.Message)
}
|