欢迎进入 gRPC 的开发文档,gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。
gRPC 是什么?
在 gRPC 里_客户端_应用可以像调用本地对象一样直接调用另一台不同的机器上_服务端_应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个_服务_,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个_存根_能够像服务端一样的方法。
gRPC 客户端和服务端可以在多种环境中运行和交互 - 从 google 内部的服务器到你自己的笔记本,并且可以用任何 gRPC 支持的语言来编写。所以,你可以很容易地用 Java 创建一个 gRPC 服务端,用 Go、Python、Ruby 来创建客户端。此外,Google 最新 API 将有 gRPC 版本的接口,使你很容易地将 Google 的功能集成到你的应用里。
protocol buffers
gRPC 默认使用 _protocol buffers_,这是 Google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)。正如你将在下方例子里所看到的,你用 proto files 创建 gRPC 服务,用 protocol buffers 消息类型来定义方法参数和返回类型。你可以在 Protocol Buffers 文档找到更多关于 Protocol Buffers 的资料。
Protocol buffers 版本
尽管 protocol buffers 对于开源用户来说已经存在了一段时间,例子内使用的却一种名叫 proto3 的新风格的 protocol buffers,它拥有轻量简化的语法、一些有用的新功能,并且支持更多新语言。当前针对 Java 和 C++ 发布了 beta 版本,针对 JavaNano(即 Android Java)发布 alpha 版本,在protocol buffers Github 源码库里有 Ruby 支持, 在golang/protobuf Github 源码库里还有针对 Go 语言的生成器, 对更多语言的支持正在开发中。 你可以在 proto3 语言指南里找到更多内容, 在与当前默认版本的发布说明比较,看到两者的主要不同点。更多关于 proto3 的文档很快就会出现。虽然你_可以_使用 proto2 (当前默认的 protocol buffers 版本), 我们通常建议你在 gRPC 里使用 proto3,因为这样你可以使用 gRPC 支持全部范围的的语言,并且能避免 proto2 客户端与 proto3 服务端交互时出现的兼容性问题,反之亦然。
Protocol Buffer 官网文档整理
gRPC 使用
环境搭建
1. protoc安装
git下载地址:https://github.com/protocolbuffers/protobuf/releases
安装完成后,使用protoc –version
2.Go插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1go get google.golang.org/grpc
使用
1. 准备工作
编写proto文件
syntax = "proto3";
// 服务端流模式
option go_package = "./;streamprotodemo";
service Greeter {
rpc Single(StreamReqData) returns (StreamResData); //单向调用
rpc GetStream(StreamReqData) returns (stream StreamResData); // 服务端流模式
rpc PutStream(stream StreamReqData) returns (StreamResData); // 客户端流模式
rpc AllStream(stream StreamReqData) returns (stream StreamResData); // 双向流模式
}
message StreamReqData {
string data =1 ;
}
message StreamResData {
string data =1 ;
}
生成grpc-go文件
protoc -I . –go_out=. stream.proto –go-grpc_out=. stream.proto
2. 单向调用
服务端代码:
package main
import (
"context"
"fmt"
"log"
"net"
"google.golang.org/grpc"
pb "grpc-study/demo2/proto"
)
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) Single(ctx context.Context, req *pb.StreamReqData) (*pb.StreamResData, error) {
fmt.Println("server Single 被调用")
return &pb.StreamResData{
Data: req.Data + "from server",
}, nil
}
func main() {
listen, err := net.Listen("tcp", ":9090")
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
// 4、创建监听
err = s.Serve(listen)
if err != nil {
fmt.Println(err.Error())
}
}
客户端代码
package main
import (
"context"
"fmt"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "grpc-study/demo2/proto"
)
func main() {
// 1、创建一个链接
conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
fmt.Println(err.Error())
}
defer conn.Close()
// 2、创建一个客户端
client := pb.NewGreeterClient(conn)
StreamReqData := &pb.StreamReqData{}
StreamReqData.Data = "client single func "
res, err := client.Single(context.Background(), StreamReqData)
if err != nil {
log.Fatalln("single call fail ", err)
}
fmt.Println("Single call res :", res)
}
3. 客户端流模式
服务端代码:
package main
import (
"fmt"
"log"
"net"
"google.golang.org/grpc"
pb "grpc-study/demo2/proto"
)
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) PutStream(clientdata pb.Greeter_PutStreamServer) error {
fmt.Println("server PutStream 被调用")
for {
a, err := clientdata.Recv()
if err != nil {
log.Println(err)
break
}
fmt.Println(a)
}
return nil
}
func main() {
listen, err := net.Listen("tcp", ":9090")
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
// 4、创建监听
err = s.Serve(listen)
if err != nil {
fmt.Println(err.Error())
}
}
客户端代码
package main
import (
"context"
"fmt"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "grpc-study/demo2/proto"
)
func main() {
// 1、创建一个链接
conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
fmt.Println(err.Error())
}
defer conn.Close()
// 2、创建一个客户端
client := pb.NewGreeterClient(conn)
StreamReqData := &pb.StreamReqData{}
putres, _ := client.PutStream(context.Background())
for i := 0; i < 10; i++ {
StreamReqData.Data = fmt.Sprintf("NO:%d, 客户端流模式", i)
fmt.Println("向服务端发送", StreamReqData.Data)
err := putres.Send(StreamReqData)
if err != nil {
log.Fatalln("向服务端发送数据失败", err)
}
}
}
4. 服务端流模式
服务端代码:
package main
import (
"fmt"
"log"
"net"
"time"
"google.golang.org/grpc"
pb "grpc-study/demo2/proto"
)
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) GetStream(req *pb.StreamReqData, res pb.Greeter_GetStreamServer) error {
fmt.Println("server GetStream 被调用")
for i := 0; i < 10; i++ {
err := res.Send(&pb.StreamResData{
Data: fmt.Sprintf("%v", time.Now().Unix()),
})
if err != nil {
log.Fatalln("send err", err)
}
time.Sleep(time.Second)
}
return nil
}
func main() {
listen, err := net.Listen("tcp", ":9090")
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
// 4、创建监听
err = s.Serve(listen)
if err != nil {
fmt.Println(err.Error())
}
}
客户端代码
package main
import (
"context"
"fmt"
"io"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "grpc-study/demo2/proto"
)
func main() {
// 1、创建一个链接
conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
fmt.Println(err.Error())
}
defer conn.Close()
// 2、创建一个客户端
client := pb.NewGreeterClient(conn)
StreamReqData := &pb.StreamReqData{}
//服务端流模式
res, _ := client.GetStream(context.TODO(), StreamReqData)
for {
data, err := res.Recv()
if err != nil {
if err == io.EOF {
fmt.Println("服务端传输结束")
break
}
log.Fatalln(err)
}
fmt.Println(data)
}
}
4. 双向流模式
服务端代码:
package main
import (
"fmt"
"log"
"net"
"sync"
"time"
"google.golang.org/grpc"
pb "grpc-study/demo2/proto"
)
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) AllStream(alldata pb.Greeter_AllStreamServer) error {
fmt.Println("server AllStream 被调用")
wg := sync.WaitGroup{}
wg.Add(2)
defer wg.Wait()
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
data, _ := alldata.Recv()
fmt.Println("AllStream收到客户端消息", data)
}
}()
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
_ = alldata.Send(&pb.StreamResData{
Data: "我是服务器",
})
time.Sleep(time.Second)
}
}()
return nil
}
func main() {
listen, err := net.Listen("tcp", ":9090")
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
// 4、创建监听
err = s.Serve(listen)
if err != nil {
fmt.Println(err.Error())
}
}
客户端代码
package main
import (
"context"
"fmt"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "grpc-study/demo2/proto"
)
func AllStream(client pb.GreeterClient, StreamReqData *pb.StreamReqData) {
//双向流模式
allstream, _ := client.AllStream(context.Background())
wg := sync.WaitGroup{}
wg.Add(2)
defer wg.Wait()
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
StreamResData, _ := allstream.Recv()
fmt.Println("AllStream收到服务端消息", StreamResData.Data)
}
}()
go func() {
defer wg.Done()
StreamReqData.Data = "AllStream:我是客户端"
for i := 0; i < 10; i++ {
_ = allstream.Send(StreamReqData)
time.Sleep(time.Second)
}
}()
wg.Wait()
}
func main() {
// 1、创建一个链接
conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
fmt.Println(err.Error())
}
defer conn.Close()
// 2、创建一个客户端
client := pb.NewGreeterClient(conn)
StreamReqData := &pb.StreamReqData{}
AllStream(client, StreamReqData)
}
5.grpc中的metadata
gRPC让我们可以像本地调用一样实现远程调用,对于每一次的RPC调用中,都可能会有一些有用的数据,而这些数据就可以通过metadata来传递。metadata是以key-value的形式存储数据的,其中key是string类型,而value是[]string,即一个字符串数组类型。metadata使得client和server能够为对方提供关于本次调用的一些信息,就像一次http请求的RequestHeader和ResponseHeader一样。http中header的生命周周期是一次http请求,那么metadata的生命周期就是一次RPC调用。
服务端
func (h *HelloServer)SayHello(ctx context.Context,req *metadatademo.HelloRequest) (*metadatademo.HelloReply,error){
md , ok := metadata.FromIncomingContext(ctx)
if !ok {
fmt.Println("获取md失败")
}
if name,ok := md["name"];ok {
fmt.Println(name[0])
}
//for key,val := range md {
// fmt.Println(key,val)
//}
return &metadatademo.HelloReply{
Message: "Hello " + req.Name,
},nil
}
客户端
func main(){
//md := metadata.Pairs("timestamp",time.Now().Format(timeformat))
lis ,_ := grpc.Dial("127.0.0.1:8081",grpc.WithInsecure())
client := metadatademo.NewHelloServiceClient(lis)
req := metadatademo.HelloRequest{Name: "chenteng"}
md := metadata.New(map[string]string{
"name":"chenteng",
})
ctx := metadata.NewOutgoingContext(context.Background(),md)
res,_ := client.SayHello(ctx,&req)
fmt.Println(res.Message)
}
6. grpc中使用拦截器传递metadata
在Grpc中可使用拦截器来进行服务鉴权,在收到请求之前就对metadata中进行校验,斌不会侵入我们的业务代码
服务端
package main
import (
"context"
"errors"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"log"
"mygrpc/protodemo"
"net"
"time"
)
type HelloServer struct {
protodemo.UnimplementedHelloServiceServer
}
func (h *HelloServer) SayHello(ctx context.Context, req *protodemo.HelloRequest) (*protodemo.HelloReply, error) {
time.Sleep(time.Second)
return &protodemo.HelloReply{
Message: "Hello " + req.Name,
}, nil
}
func main() {
Interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
if md, ok := metadata.FromIncomingContext(ctx); !ok {
log.Println("获取认证信息失败")
return nil, errors.New("获取认证信息失败")
} else {
if value, ok := md["name"]; !ok {
log.Println("认证信息中未存放name相关信息")
return nil, errors.New("认证信息中未存放name相关信息")
} else {
log.Println("客户端访问,name = ", value[0])
}
}
starttime := time.Now()
res, err := handler(ctx, req)
fmt.Println("请求已完成,耗时:", time.Since(starttime))
return res, err
}
md := grpc.UnaryInterceptor(Interceptor)
server := grpc.NewServer(md)
protodemo.RegisterHelloServiceServer(server, &HelloServer{})
lis, err := net.Listen("tcp", ":7789")
if err != nil {
log.Fatal(err)
return
}
_ = server.Serve(lis)
}
客户端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"mygrpc/protodemo"
)
type customCredential struct {}
func (c *customCredential)GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error){
return map[string]string{
"name":"chenteng",
},nil
}
func (c *customCredential)RequireTransportSecurity() bool{
//是否启用安全模式
return false
}
func main() {
//使用更简单的方式传递md
op := grpc.WithPerRPCCredentials(&customCredential{})
conn, _ := grpc.Dial("127.0.0.1:7789", grpc.WithInsecure(), op)
client := protodemo.NewHelloServiceClient(conn)
req := protodemo.HelloRequest{Name: "chenteng"}
res, _ := client.SayHello(context.Background(), &req)
fmt.Println(res.Message)
}
7.GRPC返回报错状态码与内容
在开发过程中,我们可以使用grpc下面的status包来返回类似于http中的404,500之类的错误状态码,可以方便我们排查问题,光有状态码是不够的,需要携带一下报错信息,下面是client与server的具体实现
服务端
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"log"
"mygrpc/protodemo"
"net"
)
type HelloServer struct {
protodemo.UnimplementedHelloServiceServer
}
func (h *HelloServer) SayHello(ctx context.Context, req *protodemo.HelloRequest) (*protodemo.HelloReply, error) {
return nil,status.Error(codes.Internal,"内部错误")
//return &protodemo.HelloReply{
// Message: "Hello " + req.Name,
//}, nil
}
func main() {
server := grpc.NewServer()
protodemo.RegisterHelloServiceServer(server, &HelloServer{})
lis, err := net.Listen("tcp", ":7790")
defer lis.Close()
if err != nil {
log.Fatal(err)
return
}
_ = server.Serve(lis)
}
客户端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
"mygrpc/protodemo"
)
func main() {
lis, _ := grpc.Dial("127.0.0.1:7789", grpc.WithInsecure())
client := protodemo.NewHelloServiceClient(lis)
req := protodemo.HelloRequest{Name: "chenteng"}
res, err := client.SayHello(context.Background(), &req)
if err != nil {
if statusinfo,ok := status.FromError(err);ok {
fmt.Println(statusinfo.Code())
fmt.Println(statusinfo.Message())
}
}
fmt.Println(res)
}
参考原文链接[陈小c]:https://blog.csdn.net/weixin_43143310/article/details/125064588