GRPC小小入门

GRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。GRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.
在 gRPC里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC系统类似, gRPC也是基于以下理念:

  • 定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。
  • 在服务端实现这个接口,并运行一个 gRPC服务器来处理客户端调用。

在客户端拥有一个存根能够像服务端一样的方法。 gRPC客户端和服务端可以在多种环境中运行和交互 -从 google内部的服务器到你自己的笔记本,并且可以用任何 gRPC支持的语言 来编写。
所以,你可以很容易地用 Java创建一个 gRPC服务端,用 Go、 Python、Ruby来创建客户端。此外, Google最新 API将有 gRPC版本的接口,使你很容易地将 Google的功能集成到你的应用里。
参考资料
gRPC 官方文档中文版:http://doc.oschina.net/grpc?t=60133
gRPC官网:https://grpc.io
再详细了解使用GRPC之前先来了解一下上面定义中的一些关键词。
首先我们来看一下HTTP/2是什么内容?
其实本质上就是http2.0版本,http目前为止主要有四个版本,分别为http1.0、http1.1、http2.0、https。
http1.0是最原始的版本,不支持持久连接,效率也比较低
http1.1针对http1.0版本做了优化,可以连接一次,多次使用,效率比http1.0高
http2.0实现了多路复用,对http1.1再次进行了优化。http2.0也被称为下一代http协议,是在2013年8月进行首次测试,所以现在用的不是很广。
https其实是在http协议上多加了一层SSL协议,具体如下图:
image.png
所以本质上,http1.0、http1.1、http2.0都可以添加SSL协议。

grpc环境安装

  1. 官方推荐安装方法:go get -u -v google.golang.org/grpc

但是由于一些原因,大部分同学不能直接访问google官网,所以只能曲线救国了

  1. 通过github下载各种依赖库,然后配置。 ```go 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

  1. 网络畅通可以用上述方法,但如果网速较慢,我们也可以选择离线安装方法。
  2. 3. x.zipgoogle.golang.org.zip两个离线包来安装。
  3. ```go
  4. #将x.zip 解压到 $GOPATH/src/golang.org/x 目录下
  5. $ unzip x.zip -d $GOPATH/src/golang.org/x
  6. #将google.golang.org.zip 解压到 $GOPATH/src/google.golang.org 目录下
  7. $ unzip google.golang.org.zip -d $GOPATH/src/google.golang.org
  8. #然后进入到$GOPATH/src/google.golang.org/grpc下面执行go install
  9. $ go install

GRPC使用

如果从Protobuf的角度看,GRPC只不过是一个针对service接口生成代码的生成器。接着我们来学习一下GRPC的用法。这里我们创建一个简单的proto文件,定义一个HelloService接口:

  1. syntax = "proto3"; //指定版本信息
  2. package pb; //后期生成go文件的包名
  3. message Person{
  4. // 名字
  5. string name = 1;
  6. // 年龄
  7. int32 age = 2 ;
  8. }
  9. //定义RPC服务
  10. service HelloService {
  11. rpc Hello (Person)returns (Person);
  12. }

对proto文件进行编译:
$ protoc --go_out=plugins=grpc:. *.proto
GRPC插件会为服务端和客户端生成不同的接口:

  1. //客户端接口
  2. type HelloServiceClient interface {
  3. Hello(ctx context.Context, in *Person, opts ...grpc.CallOption) (*Person, error)
  4. }
  5. //服务器接口
  6. type HelloServiceServer interface {
  7. Hello(context.Context, *Person) (*Person, error)
  8. }

我们接着可以基于他们给的服务端接口重新实现HelloService服务:

  1. type HelloService struct{}
  2. func (this*HelloService)Hello(ctx context.Context, person *pb.Person) (*pb.Person, error){
  3. reply := &pb.Person{
  4. Name:"zhangsan" + person.Name,
  5. Age:18,
  6. }
  7. return reply,nil
  8. }

GRPC的启动流程和RPC的启动流程类似,代码如下:

  1. func main(){
  2. //获取grpc服务端对象
  3. grpcServer := grpc.NewServer()
  4. //注册grpc服务
  5. pb.RegisterHelloServiceServer(grpcServer,new(HelloService))
  6. //设置服务端监听
  7. lis,err := net.Listen("tcp",":1234")
  8. if err != nil {
  9. panic(err)
  10. }
  11. //在指定端口上提供grpc服务
  12. grpcServer.Serve(lis)
  13. }

然后我们就可以通过客户端来连接GRPC服务了:

  1. func main(){
  2. //和grpc服务建立连接
  3. conn,err := grpc.Dial("localhost:1234",grpc.WithInsecure())
  4. if err != nil {
  5. panic(err)
  6. }
  7. defer conn.Close()
  8. client := pb.NewHelloServiceClient(conn)
  9. reply,err := client.Hello(context.Background(),&pb.Person{Name:"lisi",Age:100})
  10. if err != nil {
  11. panic(err)
  12. }
  13. fmt.Println("reply,",reply)
  14. }

服务端

  1. // 定义类
  2. type Children struct {
  3. }
  4. // 按接口绑定类方法
  5. func (this *Children) SayHello(ctx context.Context, t *pb.Teacher) (*pb.Teacher, error) {
  6. t.Name += " is Sleeping"
  7. return t, nil
  8. }
  9. func main() {
  10. //1. 初始一个 grpc 对象
  11. grpcServer := grpc.NewServer()
  12. //2. 注册服务
  13. pb.RegisterSayNameServer(grpcServer, new(Children))
  14. //3. 设置监听, 指定 IP、port
  15. listener, err := net.Listen("tcp", "127.0.0.1:8800")
  16. if err != nil {
  17. fmt.Println("Listen err:", err)
  18. return
  19. }
  20. defer listener.Close()
  21. //4. 启动服务。---- serve()
  22. grpcServer.Serve(listener)
  23. }

客户端

  1. package main
  2. import (
  3. "google.golang.org/grpc"
  4. "fmt"
  5. "day01/pb"
  6. "context"
  7. )
  8. func main() {
  9. //1. 连接 grpc 服务
  10. grpcConn, err := grpc.Dial("127.0.0.1:8800", grpc.WithInsecure())
  11. if err != nil {
  12. fmt.Println("grpc.Dial err:", err)
  13. return
  14. }
  15. defer grpcConn.Close()
  16. //2. 初始化 grpc 客户端
  17. grpcClient := pb.NewSayNameClient(grpcConn)
  18. // 创建并初始化Teacher 对象
  19. var teacher pb.Teacher
  20. teacher.Name = "itcast"
  21. teacher.Age = 18
  22. //3. 调用远程服务。
  23. t, err := grpcClient.SayHello(context.TODO(), &teacher)
  24. fmt.Println(t, err)
  25. }

protobuf

  1. syntax = "proto3";
  2. package pb;
  3. // 消息体 --- 一个package 中,不允许定义同名的消息体
  4. message Teacher {
  5. int32 age = 1;
  6. string name = 2;
  7. }
  8. // 定义 服务
  9. service SayName {
  10. rpc SayHello (Teacher) returns (Teacher);
  11. }
  1. type SayNameClient interface {
  2. SayHello(ctx context.Context, in *Teacher, opts ...grpc.CallOption) (*Teacher, error)
  3. }
  4. type sayNameClient struct {
  5. cc *grpc.ClientConn
  6. }
  7. func NewSayNameClient(cc *grpc.ClientConn) SayNameClient {
  8. return &sayNameClient{cc}
  9. }
  10. func (c *sayNameClient) SayHello(ctx context.Context, in *Teacher, opts ...grpc.CallOption) (*Teacher, error) {
  11. out := new(Teacher)
  12. err := c.cc.Invoke(ctx, "/pb.SayName/SayHello", in, out, opts...)
  13. if err != nil {
  14. return nil, err
  15. }
  16. return out, nil
  17. }
  1. type SayNameServer interface {
  2. SayHello(context.Context, *Teacher) (*Teacher, error)
  3. }
  4. // UnimplementedSayNameServer can be embedded to have forward compatible implementations.
  5. type UnimplementedSayNameServer struct {
  6. }
  7. func (*UnimplementedSayNameServer) SayHello(ctx context.Context, req *Teacher) (*Teacher, error) {
  8. return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
  9. }
  10. func RegisterSayNameServer(s *grpc.Server, srv SayNameServer) {
  11. s.RegisterService(&_SayName_serviceDesc, srv)
  12. }

电商项目学grpc和protobuf

有了rpc为什么还要学grpc?

从RPC封装哪里,可以知道,需要我们手动对客户端和服务端进行封装。grpc都是自动按照统一风格进行封装。
//1. 这些概念在grpc中都有对应
//2. 发自灵魂的拷问: server_proxy 和 client_proxy能否自动生成啊 为多种语言生成
//3. 都能满足 这个就是protobuf + grpc

什么是grpc和protobuf

grpc

gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHPC# 支持.
grpc和protobuf - 图2

protobuf

java中的dubbo dubbo/rmi/hessian messagepack 如果你懂了协议完全有能力自己去实现一个协议

  • 习惯用 Json、XML 数据存储格式的你们,相信大多都没听过Protocol Buffer
  • Protocol Buffer 其实 是 Google出品的一种轻量 & 高效的结构化数据存储格式,性能比 Json、XML 真的强!太!多!
  • protobuf经历了protobuf2和protobuf3,pb3比pb2简化了很多,目前主流的版本是pb3

image.png
上面说的压缩性能好:如下图,json和protobuf压缩同样数据,proto压缩后更短(也可以单独用,不是非要和grpc绑定用)
image.png
在proto文件中写的所有的结构体都可以在生成的对应proto的go文件中找到

  1. syntax = "proto3";
  2. option go_package = ".;proto";
  3. service Greeter {
  4. rpc SayHello (HelloRequest) returns (HelloReply);
  5. }
  6. message HelloRequest {
  7. string name = 1;
  8. }
  9. message HelloReply {
  10. string message = 1;
  11. }
  1. protoc -I . goods.proto --go_out=plugins=grpc:.
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "google.golang.org/grpc"
  6. "grpc_demo/hello"
  7. "net"
  8. )
  9. type Server struct {
  10. }
  11. //这里的context主要是对协程的超时控制或其他控制
  12. func (s *Server) SayHello(ctx context.Context,request *hello.HelloRequest)(*hello.HelloReply,error){
  13. return &hello.HelloReply{Message:"Hello "+request.Name},nil
  14. }
  15. func main() {
  16. //实例一个grpc的server
  17. g := grpc.NewServer()
  18. s := Server{}
  19. hello.RegisterGreeterServer(g,&s)
  20. listen, err := net.Listen("tcp", fmt.Sprintf(":8080"))
  21. if err != nil {
  22. panic("failed to listen: "+err.Error())
  23. }
  24. g.Serve(listen)
  25. }
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "google.golang.org/grpc"
  6. "grpc_demo/proto"
  7. )
  8. func main() {
  9. conn,err := grpc.Dial("127.0.0.1:8080",grpc.WithInsecure())
  10. if err!=nil{
  11. panic(err)
  12. }
  13. defer conn.Close()
  14. c := hello.NewGreeterClient(conn)
  15. r,err := c.SayHello(context.Background(),&hello.HelloRequest{Name:"bobby"})
  16. if err!=nil{
  17. panic(err)
  18. }
  19. fmt.Println(r.Message)
  20. }

proto文件生成go文件讲解

proto文件
image.png

首先看message转换成什么如下图:前三个小写的不用管

image.png
image.png

server端自动帮我们生成了接口,我们需要做的就是实现接口中对应的业务逻辑

image.png
image.png
image.png

grpc的四种数据流

之前我们讲了 grpc 怎么简单的使用 ,这次讲讲 grpc 中的 stream,srteam 顾名思义就是一种流,可以源源不断的推送数据,很适合传输一些大数据,或者服务端和客户端长时间数据交互,比如客户端可以向 服务端订阅 一个数据,服务端就可以利用 stream ,源源不断地推送数据。

1. 简单模式(Simple RPC)

这种模式最为传统,即客户端发起一次请求,服务端响应一个数据,这和大家平时熟悉的RPC没有什么大的区别,所以不再详细介绍。

2. 服务端数据流模式(Server-side streaming RPC)

这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端

3. 客户端数据流模式(Client-side streaming RPC)

与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。典型的例子是物联网终端向服务器报送数据。

4.双向数据流模式(Bidirectional streaming RPC)

  1. 顾名思义,这是客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是可以实现实时交互。典型的例子是聊天机器人。
  1. syntax = "proto3";//声明proto的版本 只能 是3,才支持 grpc
  2. //声明 包名
  3. option go_package=".;proto";
  4. //声明grpc服务
  5. service Greeter {
  6. /*
  7. 以下分别是 服务端 推送流, 客户端 推送流 ,双向流。
  8. */
  9. //stream是关键字,代表流。可以写{}也可以写;。一个意思
  10. rpc GetStream (StreamReqData) returns (stream StreamResData){}//服务端流模式
  11. rpc PutStream (stream StreamReqData) returns (StreamResData){}//客户端流模式
  12. rpc AllStream (stream StreamReqData) returns (stream StreamResData){}//双向流
  13. }
  14. //stream请求结构
  15. message StreamReqData {
  16. string data = 1;
  17. }
  18. //stream返回结构
  19. message StreamResData {
  20. string data = 1;
  21. }

image.png
image.png

  1. package main
  2. import (
  3. "fmt"
  4. "google.golang.org/grpc"
  5. "log"
  6. "net"
  7. "start/new_stream/proto"
  8. "sync"
  9. "time"
  10. )
  11. const PORT = ":50052"
  12. type server struct {
  13. }
  14. //服务端 单向流,这里参数和一般的不一样,没有了context,具体要根据proto以后的函数
  15. func (s *server)GetStream(req *proto.StreamReqData, res proto.Greeter_GetStreamServer) error{
  16. i:= 0
  17. for{
  18. i++
  19. //res proto.Greeter_GetStreamServer下有一个send方法,代表服务端不断发送数据
  20. res.Send(&proto.StreamResData{Data:fmt.Sprintf("%v",time.Now().Unix())})
  21. time.Sleep(1*time.Second)
  22. if i >10 {
  23. break
  24. }
  25. }
  26. return nil
  27. }
  28. //客户端 单向流
  29. func (s *server) PutStream(cliStr proto.Greeter_PutStreamServer) error {
  30. for {
  31. //接收客户端推送过来的流数据,不断打印
  32. if tem, err := cliStr.Recv(); err == nil {
  33. log.Println(tem)
  34. } else {
  35. log.Println("break, err :", err)
  36. break
  37. }
  38. }
  39. return nil
  40. }
  41. //客户端服务端 双向流
  42. func(s *server) AllStream(allStr proto.Greeter_AllStreamServer) error {
  43. wg := sync.WaitGroup{}
  44. wg.Add(2)
  45. go func() {
  46. for {
  47. data, _ := allStr.Recv()
  48. log.Println(data)
  49. }
  50. wg.Done()
  51. }()
  52. go func() {
  53. for {
  54. allStr.Send(&proto.StreamResData{Data:"ssss"})
  55. time.Sleep(time.Second)
  56. }
  57. wg.Done()
  58. }()
  59. wg.Wait()
  60. return nil
  61. }
  62. func main(){
  63. //监听端口
  64. lis,err := net.Listen("tcp",PORT)
  65. if err != nil{
  66. panic(err)
  67. return
  68. }
  69. //创建一个grpc 服务器
  70. s := grpc.NewServer()
  71. //注册事件
  72. proto.RegisterGreeterServer(s,&server{})
  73. //处理链接
  74. err = s.Serve(lis)
  75. if err != nil {
  76. panic(err)
  77. }
  78. }
  1. type GreeterClient interface {
  2. GetStream(ctx context.Context, in *StreamReqData, opts ...grpc.CallOption) (Greeter_GetStreamClient, error)
  3. PutStream(ctx context.Context, opts ...grpc.CallOption) (Greeter_PutStreamClient, error)
  4. AllStream(ctx context.Context, opts ...grpc.CallOption) (Greeter_AllStreamClient, error)
  5. }
  1. package main
  2. import (
  3. "google.golang.org/grpc"
  4. "context"
  5. _ "google.golang.org/grpc/balancer/grpclb"
  6. "log"
  7. "start/new_stream/proto"
  8. "time"
  9. )
  10. const (
  11. ADDRESS = "localhost:50052"
  12. )
  13. func main(){
  14. //通过grpc 库 建立一个连接
  15. conn ,err := grpc.Dial(ADDRESS,grpc.WithInsecure())
  16. if err != nil{
  17. return
  18. }
  19. defer conn.Close()
  20. //通过刚刚的连接 生成一个client对象。
  21. c := proto.NewGreeterClient(conn)
  22. //调用服务端推送流
  23. reqstreamData := &proto.StreamReqData{Data:"aaa"}
  24. res,_ := c.GetStream(context.Background(),reqstreamData)//这句也是,之前没有的
  25. for {
  26. aa,err := res.Recv()//接收数据流
  27. if err != nil {
  28. log.Println(err)
  29. break
  30. }
  31. log.Println(aa)
  32. }
  33. //客户端 推送 流
  34. putRes, _ := c.PutStream(context.Background())
  35. i := 1
  36. for {
  37. i++
  38. putRes.Send(&proto.StreamReqData{Data:"ss"})
  39. time.Sleep(time.Second)
  40. if i > 10 {
  41. break
  42. }
  43. }
  44. //服务端 客户端 双向流
  45. allStr,_ := c.AllStream(context.Background())
  46. go func() {
  47. for {
  48. data,_ := allStr.Recv()
  49. log.Println(data)
  50. }
  51. }()
  52. go func() {
  53. for {
  54. allStr.Send(&proto.StreamReqData{Data:"ssss"})
  55. time.Sleep(time.Second)
  56. }
  57. }()
  58. select {
  59. }
  60. }

protobuf相关知识

image.png
image.png
image.png

proto文件同步时

当message里成员编码在调用微服务的时候,复制proto过去的时候,成员编码搞反了。返回的结果也是反的,因为protobuf是为了压缩空间根据编号进行处理的。

proto文件如果import另外一个proto里的message?

先建一个base的proto文件,把公共的message放进去如下图,Empty和Pong是调用别的文件的。
image.png
image.png

嵌套message

repeated是数组
嵌套 的时候,编码是独立的,不冲突
实例化使用Result时,名字是改变的,记得去proto.go中看看叫啥
image.png
image.png

枚举类型

image.png
转化后
image.png
使用枚举时,我们定义值的时候在枚举的里面选择一个值进行定义
image.png

map类型

image.png左k右v
转化后
image.png
image.png

时间戳timestamp

image.png
image.png
image.png
image.png

grpc小知识

go控制grpc的metadata

image.png
image.png
image.png

  1. syntax = "proto3";
  2. option go_package = ".;proto";
  3. service Greeter {
  4. rpc SayHello (HelloRequest) returns (HelloReply);
  5. }
  6. //将 sessionid放入 放入cookie中 http协议
  7. message HelloRequest {
  8. string name = 1;
  9. }
  10. message HelloReply {
  11. string message = 1;
  12. }
  13. //go语言中是生成一个文件, 也就只有python会生成两个文件
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "google.golang.org/grpc/metadata"
  6. "net"
  7. "google.golang.org/grpc"
  8. "OldPackageTest/grpc_test/proto"
  9. )
  10. type Server struct{}
  11. func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
  12. error) {
  13. md, ok := metadata.FromIncomingContext(ctx)
  14. if ok {
  15. fmt.Println("get metadata error")
  16. }
  17. if nameSlice, ok := md["name"]; ok {
  18. fmt.Println(nameSlice)
  19. for i, e := range nameSlice {
  20. fmt.Println(i, e)
  21. }
  22. }
  23. return &proto.HelloReply{
  24. Message: "hello " + request.Name,
  25. }, nil
  26. }
  27. func main() {
  28. g := grpc.NewServer()
  29. proto.RegisterGreeterServer(g, &Server{})
  30. lis, err := net.Listen("tcp", "0.0.0.0:50051")
  31. if err != nil {
  32. panic("failed to listen:" + err.Error())
  33. }
  34. err = g.Serve(lis)
  35. if err != nil {
  36. panic("failed to start grpc:" + err.Error())
  37. }
  38. }
  1. package main
  2. import (
  3. "OldPackageTest/grpc_test/proto"
  4. "context"
  5. "fmt"
  6. "google.golang.org/grpc"
  7. "google.golang.org/grpc/metadata"
  8. )
  9. func main() {
  10. //stream
  11. conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())
  12. if err != nil {
  13. panic(err)
  14. }
  15. defer conn.Close()
  16. c := proto.NewGreeterClient(conn)
  17. //md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
  18. md := metadata.New(map[string]string{
  19. "name": "bobby",
  20. "pasword": "imooc",
  21. })
  22. ctx := metadata.NewOutgoingContext(context.Background(), md)
  23. r, err := c.SayHello(ctx, &proto.HelloRequest{Name: "bobby"})
  24. if err != nil {
  25. panic(err)
  26. }
  27. fmt.Println(r.Message)
  28. }

image.png
调用远程以后
image.png再返回去看metadata定义

grpc拦截器

记录接口的访问时常、对接口验证用户登陆的情况。对所有的请求拦截一下,进行预处理。如果把这些逻辑在每一个接口都重新写一遍,就会有很多问题,比如忘记写,比如接口本身会被污染,后面进行维护时可能会出错。所以就需要一个统一的拦截器

  1. syntax = "proto3";
  2. option go_package = ".;proto";
  3. package proto;
  4. service Greeter {
  5. rpc SayHello (HelloRequest) returns (HelloReply);
  6. }
  7. //将 sessionid放入 放入cookie中 http协议
  8. message HelloRequest {
  9. string name = 1;
  10. }
  11. message HelloReply {
  12. string message = 1;
  13. }
  14. //go语言中是生成一个文件, 也就只有python会生成两个文件
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "time"
  7. "google.golang.org/grpc"
  8. "OldPackageTest/grpc_test/proto"
  9. )
  10. type Server struct{}
  11. func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
  12. error) {
  13. time.Sleep(2 * time.Second)
  14. return &proto.HelloReply{
  15. Message: "hello " + request.Name,
  16. }, nil
  17. }
  18. func main() {
  19. interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  20. fmt.Println("接收到了一个新的请求")
  21. res, err := handler(ctx, req)
  22. fmt.Println("请求已经完成")
  23. return res, err
  24. }
  25. opt := grpc.UnaryInterceptor(interceptor)
  26. //拦截器在启动newserver时提前配置好
  27. g := grpc.NewServer(opt)
  28. proto.RegisterGreeterServer(g, &Server{})
  29. lis, err := net.Listen("tcp", "0.0.0.0:50051")
  30. if err != nil {
  31. panic("failed to listen:" + err.Error())
  32. }
  33. err = g.Serve(lis)
  34. if err != nil {
  35. panic("failed to start grpc:" + err.Error())
  36. }
  37. }
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "google.golang.org/grpc/codes"
  7. grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
  8. "google.golang.org/grpc"
  9. "OldPackageTest/grpc_test/proto"
  10. )
  11. func main() {
  12. //stream
  13. interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  14. start := time.Now()//本次调用一共花了多少时间
  15. err := invoker(ctx, method, req, reply, cc, opts...)
  16. fmt.Printf("耗时:%s\n", time.Since(start))
  17. return err
  18. }
  19. var opts []grpc.DialOption
  20. opts = append(opts, grpc.WithInsecure())
  21. retryOpts := []grpc_retry.CallOption{
  22. grpc_retry.WithMax(3),
  23. grpc_retry.WithPerRetryTimeout(1 * time.Second),
  24. grpc_retry.WithCodes(codes.Unknown, codes.DeadlineExceeded, codes.Unavailable),
  25. }
  26. opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
  27. //这个请求应该多长时间超时, 这个重试应该几次、当服务器返回什么状态码的时候重试
  28. opts = append(opts, grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOpts...)))
  29. conn, err := grpc.Dial("127.0.0.1:50051", opts...)
  30. if err != nil {
  31. panic(err)
  32. }
  33. defer conn.Close()
  34. c := proto.NewGreeterClient(conn)
  35. r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
  36. if err != nil {
  37. panic(err)
  38. }
  39. fmt.Println(r.Message)
  40. }

meradata和拦截器实现用户登录验证功能案例

为了安全性,不让所有人都能访问,实现一个用户登录的验证。
用户名和密码不适合携带在message中,因为很多接口都要的,每个message都要带的话,太罗嗦和麻烦。
拦截器可以不侵入业务逻辑,而把功能集成进来。使用metadata可以不侵入message将数据完整传送过来。

  1. syntax = "proto3";
  2. option go_package = ".;proto";
  3. service Greeter {
  4. rpc SayHello (HelloRequest) returns (HelloReply);
  5. }
  6. //将 sessionid放入 放入cookie中 http协议
  7. //这个就好比文档,表单验证
  8. message HelloRequest {
  9. string name = 1;
  10. }
  11. message HelloReply {
  12. string message = 1;
  13. }
  14. //go语言中是生成一个文件, 也就只有python会生成两个文件
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "google.golang.org/grpc/codes"
  6. "google.golang.org/grpc/metadata"
  7. "google.golang.org/grpc/status"
  8. "net"
  9. "google.golang.org/grpc"
  10. "OldPackageTest/grpc_test/proto"
  11. )
  12. type Server struct{}
  13. func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
  14. error) {
  15. return &proto.HelloReply{
  16. Message: "hello " + request.Name,
  17. }, nil
  18. }
  19. func main() {
  20. interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  21. fmt.Println("接收到了一个新的请求")
  22. md, ok := metadata.FromIncomingContext(ctx)
  23. fmt.Println(md)
  24. if !ok {
  25. //已经开始接触到grpc的错误处理了
  26. return resp, status.Error(codes.Unauthenticated, "无token认证信息")
  27. }
  28. var (
  29. appid string
  30. appkey string
  31. )
  32. if va1, ok := md["appid"]; ok {
  33. appid = va1[0]
  34. }
  35. if va1, ok := md["appkey"]; ok {
  36. appkey = va1[0]
  37. }
  38. if appid != "101010" || appkey != "i am key" {
  39. return resp, status.Error(codes.Unauthenticated, "无token认证信息")
  40. }
  41. res, err := handler(ctx, req)
  42. fmt.Println("请求已经完成")
  43. return res, err
  44. }
  45. opt := grpc.UnaryInterceptor(interceptor)
  46. g := grpc.NewServer(opt)
  47. proto.RegisterGreeterServer(g, &Server{})
  48. lis, err := net.Listen("tcp", "0.0.0.0:50051")
  49. if err != nil {
  50. panic("failed to listen:" + err.Error())
  51. }
  52. err = g.Serve(lis)
  53. if err != nil {
  54. panic("failed to start grpc:" + err.Error())
  55. }
  56. }
  1. package main
  2. import (
  3. "OldPackageTest/grpc_test/proto"
  4. "context"
  5. "fmt"
  6. "google.golang.org/grpc"
  7. )
  8. type customCredential struct{}
  9. func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
  10. return map[string]string{
  11. "appid": "101010",
  12. "appkey": "i am key",
  13. }, nil
  14. }
  15. // RequireTransportSecurity indicates whether the credentials requires
  16. // transport security.
  17. func (c customCredential) RequireTransportSecurity() bool {
  18. return false
  19. }
  20. func main() {
  21. //stream
  22. //interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error{
  23. // start := time.Now()
  24. // //md := metadata.New(map[string]string{
  25. // // "appid":"10101",
  26. // // "appkey":"i am key",
  27. // //})
  28. // ctx = metadata.NewOutgoingContext(context.Background(), md)
  29. // err := invoker(ctx, method, req, reply, cc, opts...)
  30. // fmt.Printf("耗时:%s\n", time.Since(start))
  31. // return err
  32. //}
  33. grpc.WithPerRPCCredentials(customCredential{})
  34. var opts []grpc.DialOption
  35. opts = append(opts, grpc.WithInsecure())
  36. opts = append(opts, grpc.WithPerRPCCredentials(customCredential{}))
  37. conn, err := grpc.Dial("127.0.0.1:50051", opts...)
  38. if err != nil {
  39. panic(err)
  40. }
  41. defer conn.Close()
  42. c := proto.NewGreeterClient(conn)
  43. r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
  44. if err != nil {
  45. panic(err)
  46. }
  47. fmt.Println(r.Message)
  48. }

grpc异常处理

proto和前面都一样

  1. package main
  2. import (
  3. "context"
  4. "google.golang.org/grpc/codes"
  5. "google.golang.org/grpc/status"
  6. "net"
  7. "google.golang.org/grpc"
  8. "OldPackageTest/grpc_error_test/proto"
  9. )
  10. type Server struct{}
  11. func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
  12. error) {
  13. return nil, status.Errorf(codes.NotFound, "记录未找到:%s", request.Name)
  14. //return &proto.HelloReply{
  15. // Message: "hello "+request.Name,
  16. //}, nil
  17. }
  18. func main() {
  19. g := grpc.NewServer()
  20. proto.RegisterGreeterServer(g, &Server{})
  21. lis, err := net.Listen("tcp", "0.0.0.0:50051")
  22. if err != nil {
  23. panic("failed to listen:" + err.Error())
  24. }
  25. err = g.Serve(lis)
  26. if err != nil {
  27. panic("failed to start grpc:" + err.Error())
  28. }
  29. }
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "google.golang.org/grpc"
  6. "google.golang.org/grpc/status"
  7. "time"
  8. "OldPackageTest/grpc_error_test/proto"
  9. )
  10. func main() {
  11. //stream
  12. conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())
  13. if err != nil {
  14. panic(err)
  15. }
  16. defer conn.Close()
  17. c := proto.NewGreeterClient(conn)
  18. //go语言推荐的是返回一个error和一个正常的信息
  19. ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
  20. _, err = c.SayHello(ctx, &proto.HelloRequest{Name: "bobby"})
  21. if err != nil {
  22. st, ok := status.FromError(err)
  23. if !ok {
  24. // Error was not a status error
  25. panic("解析error失败")
  26. }
  27. fmt.Println(st.Message())
  28. fmt.Println(st.Code())
  29. }
  30. //fmt.Println(r.Message)
  31. }

grpc超时机制

image.png
image.png