GRPC詳解

ice_moss發表於2022-05-19

文章介紹

在閱讀本文之前建議您先閱讀RPC核心概念理解GRPC之protobuf理解,在本文中我們不再過多介紹GRPC的基礎部分,主要介紹:grpc的metadata機制、GRPC攔截器、透過攔截器和metadata實現GRPC的auth認證、grpc狀態碼、grpc的超時機制和protoc檔案生成的go原始碼解析

GRPC的metadata機制

  • metadata的建立
    1. 使用metadata.New()

      這裡需要注意:返回的metadata.New()返回的是型別:type MD map[string][]string,而我們的metadata.New()方法是入參是:map[string]string,下面我們來看看metadata.New()方法的實現:

      type MD map[string][]string
      
      func New(m map[string]string) MD {
          md := MD{}
          for k, val := range m {
              key := strings.ToLower(k)
              md[key] = append(md[key], val)
          }
          return md
      }

      示例:

      md := metadata.New(map[string]string{
              "name":     "ice_moss",
              "password": "ice_12345",
          })
  1. 使用metadata.Pairs()

    同樣我們可以來檢視一下原始碼:

    type MD map[string][]string
    
    func Pairs(kv ...string) MD {
        if len(kv)%2 == 1 {
            panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv)))
        }
        md := MD{}
        for i := 0; i < len(kv); i += 2 {
            key := strings.ToLower(kv[i])
            md[key] = append(md[key], kv[i+1])
        }
        return md
    }

    仔細看metadata.Pairs()的原始碼可以看出我們的入參個數必須是偶數,並且每兩兩為鍵值對key-value

    示例:

    md := metadata.Pairs(
        "name", "ice_moss",
        "password", "ice_moss12345"
    )

    即:

    name: ice_moss
    password: ice_moss12345
  • metadata資料的傳送和接收

    在此之前我們先來定義一個proto檔案,來滿足我們下面的需要的metadata資料使用場景

    Proto:

    syntax = "proto3";
    
    option go_package="/.;proto";
    //引入protobuf的內建型別
    import "google/protobuf/timestamp.proto";
    
    //定義介面
    service Greeter {
        rpc SayHello (HelloRequest) returns (HelloReply);
    }
    
    //列舉型別
    enum Gender{
        MALE = 0;
        FE_MALE = 1;
    }
    
    message HelloRequest {
        string name = 1;
        string url = 2;
        Gender gender = 3;
        map<string, string> m = 4;  //proto map型別
        google.protobuf.Timestamp addTime = 5;  //protobuf的內建型別
    }
    
    message HelloReply {
        string id = 1;
        HelloRequest request = 2;
    }

    以上程式碼有不理解的可以閱讀:GRPC之protobuf理解

    這裡我們來模擬客服端client和服務端server進行互動,來進行metadata資料的傳送和接收,這裡我們需要知道的是metadata是透過go語言中的context的上下文來傳輸的

    server:
    package main
    
    import (
        "context"
        "log"
        "net"
        "rpcstudy/metadata_test/proto"
    
        "google.golang.org/grpc"
    )
    
    type HelloSerivce struct{}
    
    //SayHello實現對使用者資料和id繫結
    func (h *HelloSerivce) SayHello(c context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
      //透過metadata.FromIncomingContext()方法獲取context的上下文
      md, ok := metadata.FromIncomingContext(c)
        if !ok {
            log.Fatal("FromIncomingContext error")
        }
        for key, value := range md {
            fmt.Printf("key:%v, value:%v\n", key, value)
        }
        return &proto.HelloReply{
            Id:      "123456789",
            Request: req,
        }, nil
    }
    
    func main() {
        //例項化server
        lit, err := net.Listen("tcp", ":9090")
        if err != nil {
            log.Panicln("監聽失敗", err)
        }
    
      //建立grpc伺服器
        s := grpc.NewServer()  
      //註冊處理邏輯
        proto.RegisterGreeterServer(s, &HelloSerivce{})
    
        //開啟服務,這裡理解為將連線連入grpc伺服器中,就可以完成通訊
        log.Println(s.Serve(lit))
    }

    寫完server並執行server,等待請求進入

    client:
    package main
    
    import (
        "context"
        "fmt"
        "log"
        "rpcstudy/metadata_test/proto"
        "time"
    
        "google.golang.org/grpc/metadata"
    
        "google.golang.org/grpc"
        timepb "google.golang.org/protobuf/types/known/timestamppb"
    )
    
    func main() {
      //使用grpc.Dial()進行撥號, grpc.WithInsecure()使用不安全的方式連線
        clientConn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
        if err != nil {
            log.Panicln("連線失敗", err)
        }
        defer clientConn.Close()
    
        md := metadata.New(map[string]string{
            "name":     "ice_moss",
            "password": "ice_12345",
        })
    
        //傳送metadata
        //NewOutgoingContext建立一個新的上下文,並附加了md放入context中,並返回context
        //即:新建一個有metada的context
        ctx := metadata.NewOutgoingContext(context.Background(), md)
    
        //連線grpc伺服器
        client := proto.NewGreeterClient(clientConn)
        //單向傳送至server,將帶有md的上下文ctx入參
        res, err := client.SayHello(ctx, &proto.HelloRequest{
            Name:   "kuangyang",
            Url:    "https://learnku.com/blog/yangkuang",
            Gender: proto.Gender_MALE,
            M: map[string]string{
                "來自": "北京",
                "現居": "上海",
            },
            AddTime: timepb.New(time.Now()),
        })
        if err != nil {
            panic(err)
        }
        fmt.Printf("返回結果: %v", res)
    
    }

    執行client,最後client返回:

    返回結果: id:"123456789" request:{name:"kuangyang" url:"https://learnku.com/blog/yangkuang" m:{key:"來自" value:"北京"} m:{key:"現居" value:"上海"} addTime:{seconds929482000}}

    server輸出:

    key:user-agent, value:[grpc-go/1.46.2]
    key:name, value:[ice_moss]
    key:password, value:[ice_12345]
    key::authority, value:[localhost:9090]
    key:content-type, value:[application/grpc]

    輸出的內容中我們就可以看到客服端透過metadata請求發過來的資料,當然裡面還有一些其他的資料,其實就是我們的handler,和http的handler類似

總結一下:

建立metadata:

metadata.New()
metadata.Pairs()

將metadata資料放入context中:

//NewOutgoingContext(ctx, md)

//函式宣告
func NewOutgoingContext(ctx context.Context, md MD) context.Context {
   return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md})
}

將metadata資料從context中拿出:

metadata.FromIncomingContext(c)

//函式宣告
func FromIncomingContext(ctx context.Context) (MD, bool) {
   md, ok := ctx.Value(mdIncomingKey{}).(MD)
   if !ok {
       return nil, false
   }
   out := MD{}
   for k, v := range md {
       // We need to manually convert all keys to lower case, because MD is a
       // map, and there's no guarantee that the MD attached to the context is
       // created using our helper functions.
       key := strings.ToLower(k)
       s := make([]string, len(v))
       copy(s, v)
       out[key] = s
   }
   return out, true
}

GRPC的攔截器

我們在開發的過程中驗證使用者身份、驗證token、驗證有效時間等,我們又不想將我們在驗證機制加入到我們的業務邏輯的程式碼中,並且我們的業務邏輯不可能每一個業務都進行驗證,這時就需要攔截器來解決這類問題。而grpc為我們提供了攔截器的實現,可以方便開發者更好的開發,在執行業務邏輯之前,服務端會先執行我們實現的攔截器,對客服端資料的提取和驗證。

在metadata機制中,我們獲取metadata資料是透過我們的業務程式碼SayHell()方法來實現的,這顯然是影響到了我們的業務邏輯,業務程式碼中我們只想做業務的開發,並且不可能每一個業務都寫入驗證機制,下面我們直接來實現一個簡單的攔截器:

我們依然使用metadata機制中的proto程式碼,下面我們來寫server和client的程式碼

server:
package main

import (
    "context"
    "fmt"
    "log"
    "net"

    "rpcstudy/grpc_interpretor/proto"

    "google.golang.org/grpc"
)

type HelloSerivce struct{}

func (h *HelloSerivce) SayHello(c context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
    //此時我們不需要在 SayHello中寫攔截器邏輯了
  //接收client傳送的handle訊息
    //md, ok := metadata.FromIncomingContext(c)
    //if !ok {
    //    fmt.Println("get metadata err")
    //}
    //
    //for key, value := range md {
    //    fmt.Printf("%v: %v\n", key, value)
    //}

    return &proto.HelloReply{
        Id:      "123456789",
        Request: req,
    }, nil
}

func main() {
    //grpc在呼叫業務邏輯之前會先執行inerpertor,進行資料的攔截
    //例項化server
    lit, err := net.Listen("tcp", ":9090")
    if err != nil {
        log.Panicln("監聽失敗", err)
    }

    //實現攔截器的處理邏輯
    interpretor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {

    ma, ok := metadata.FromIncomingContext(ctx)
        if !ok {
            log.Fatal("FromIncomingContext error")
        }
        fmt.Println("metadata資料: \n", ma)

        fmt.Println("攔截到一條新訊息:\n", req)

        //我們想繼續做時間統計,不讓直接返回
        res, err := handler(ctx, req)
        fmt.Println("該請求已經完成")
        return res, err
    }

    //grpc攔截器的使用,返回一個ServerOption
    sopt := grpc.UnaryInterceptor(interpretor)

    //註冊處理邏輯
    //建立grpc服務, NewServer的引數為ServerOption型別的可選引數
    //即建立伺服器時使用可放入攔截器
    s := grpc.NewServer(sopt)
    proto.RegisterGreeterServer(s, &HelloSerivce{})

    //開啟服務
    log.Println(s.Serve(lit))

}

執行server,等待請求進入

Client:
package main

import (
    "context"
    "fmt"
    "log"
    "rpcstudy/grpc_interpretor/proto"
    "time"

    "google.golang.org/grpc/metadata"

    "google.golang.org/grpc"
    timepb "google.golang.org/protobuf/types/known/timestamppb"
)

func main() {
    //client攔截器,client的攔截器和server攔截器邏輯一致,程式碼有點出入,具體不過多介紹,具體看這部分註釋:
    //interpretor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    //    start := time.Now()
    //    md := metadata.New(map[string]string{
    //        "appid":  "ice_moss",
    //        "appkey": "ice_12345",
    //    })
    //    //NewOutgoingContext建立一個新的上下文context,並附加了md放入context中,並返回這個context
    //    ctx = metadata.NewOutgoingContext(context.Background(), md)
    //
    //    //呼叫者invoker
    //    err := invoker(ctx, method, req, reply, cc)
    //    fmt.Printf("消耗時間:%s\n", time.Since(start))
    //    return err
    //}
    //
    //opt := grpc.WithUnaryInterceptor(interpretor)

    clientConn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
    if err != nil {
        log.Panicln("連線失敗", err)
    }
    defer clientConn.Close()

    //連線grpc伺服器
    client := proto.NewGreeterClient(clientConn)

    md := metadata.New(map[string]string{
        "appid":  "ice_moss",
        "appkey": "ice_12345",
    })
    ctx := metadata.NewOutgoingContext(context.Background(), md)

    res, err := client.SayHello(ctx, &proto.HelloRequest{
        Name:   "kuangyang",
        Url:    "https://learnku.com/blog/yangkuang",
        Gender: proto.Gender_MALE,
        M: map[string]string{
            "來自": "北京",
            "現居": "上海",
        },
        AddTime: timepb.New(time.Now()),
    })
    if err != nil {
        panic(err)
    }

    fmt.Printf("返回結果: %v", res)

}

這裡我們只介紹server攔截器,client返回結果:

返回結果: id:"123456789" request:{name:"kuangyang" url:"https://learnku.com/blog/yangkuang" m:{key:"來自" value:"北京"} m:{key:"現居" value:"上海"} addTime:{seconds:165s:686279000}}

server輸出:

metadata資料: 
 map[:authority:[localhost:9090] appid:[ice_moss] appkey:[ice_12345] content-type:[application/grpc] user-agent:[grpc-go/1.46.2]]
攔截到一條新訊息:
 name:"kuangyang"  url:"https://learnku.com/blog/yangkuang"  m:{key:"來自"  value:"北京"}  m:{key:"現居"  value:"上海"}  addTime:{seconds:1652953608  nanos:686279000}
該請求已經完成

仔細看我們的server不僅拿到了metadata中的資料,也拿到了client上傳的資訊資料,接著我們就可以將這些資料進行有效驗證了

攔截器和metadata實現GRPC的auth認證

學習了上面的內容,下面我們來實現對使用者的身份和密碼的驗證這裡我們叫auth認證,同樣使用上面內容中的proto檔案,直接上程式碼:

server:
package main

import (
    "context"
    "fmt"
    "log"
    "net"

    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"

    "google.golang.org/grpc/metadata"

    "rpcstudy/grpc_interpretor/proto"

    "google.golang.org/grpc"
)

//實現介面的實現者
type HelloSerivce struct{}

//對使用者資料和id進行繫結
func (h *HelloSerivce) SayHello(c context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
    return &proto.HelloReply{
        Id:      "123456789",
        Request: req,
    }, nil
}

func main() {
    //grpc在呼叫業務邏輯之前會先執行inerpertor,進行資料的攔截

    //例項化server
    lit, err := net.Listen("tcp", ":9090")
    if err != nil {
        log.Panicln("監聽失敗", err)
    }

    //攔截器處理邏輯
    interpretor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {

        //接收client傳送的handle訊息
        md, ok := metadata.FromIncomingContext(ctx)
        if !ok {
            fmt.Println("get metadata err")
            return nil, status.Error(codes.Unauthenticated, "沒有token認證")
        }

        var appid string
        var appkey string

    //將appid的value取出
        if value, ok := md["appid"]; ok {
            appid = value[0]
        }

     //將appkey的value取出
        if value, ok := md["appkey"]; ok {
            appkey = value[0]
        }

        if appid != "ice_moss" || appkey != "ice_12345" {
            fmt.Println("使用者token未認證")
            return nil, status.Error(codes.Unauthenticated, "沒有token認證")
        }

        fmt.Println("攔截到一條新訊息:\n", req)

        //我們想繼續做時間統計,不讓直接返回
        res, err := handler(ctx, req)
        fmt.Println("該請求已經完成, 認證成功")
        return res, err
    }

    //grpc攔截器的使用,返回一個ServerOption
    sopt := grpc.UnaryInterceptor(interpretor)

    //註冊處理邏輯
    //建立grpc服務, NewServer的引數為ServerOption型別的可選引數
    //即建立伺服器時將攔截器放入伺服器中
    s := grpc.NewServer(sopt)
    proto.RegisterGreeterServer(s, &HelloSerivce{})

    //開啟服務
    log.Println(s.Serve(lit))

}

執行server, 等待請求進入

client:
package main

import (
    "context"
    "fmt"
    "log"
    "rpcstudy/grpc_interpretor/proto"
    "time"

    "google.golang.org/grpc/metadata"

    "google.golang.org/grpc"
    timepb "google.golang.org/protobuf/types/known/timestamppb"
)

func main() {
    clientConn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
    if err != nil {
        log.Panicln("連線失敗", err)
    }
    defer clientConn.Close()

    //連線grpc伺服器
    client := proto.NewGreeterClient(clientConn)

    md := metadata.New(map[string]string{
        "appid":  "ice_moss",
        "appkey": "ice_12345",
    })
    ctx := metadata.NewOutgoingContext(context.Background(), md)

    res, err := client.SayHello(ctx, &proto.HelloRequest{
        Name:   "kuangyang",
        Url:    "https://learnku.com/blog/yangkuang",
        Gender: proto.Gender_MALE,
        M: map[string]string{
            "來自": "北京",
            "現居": "上海",
        },
        AddTime: timepb.New(time.Now()),
    })
    if err != nil {
        panic(err)
    }

    fmt.Printf("返回結果: %v", res)

}

client返回:

返回結果: id:"123456789" request:{name:"kuangyang" url:"https://learnku.com/blog/yangkuang" m:{key:"來自" value:"北京"} m:{key:"現居" value:"上海"} addTime:{seconds:165nanos:883406000}}

server輸出:

攔截到一條新訊息:
 name:"kuangyang"  url:"https://learnku.com/blog/yangkuang"  m:{key:"來自"  value:"北京"}  m:{key:"現居"  value:"上海"}  addTime:{seconds:1652954947  nanos:883406000}
該請求已經完成, 認證成功

接下來我們將metadata中資料修改一下:

    md := metadata.New(map[string]string{
        "appid":  "ice_moss",
        "appkey": "ice",
    })

client返回:

panic: rpc error: code = Unauthenticated desc = 沒有token認證

Server輸出:

使用者token未認證

這就是一個簡單的auth認證過程,當然這裡也歡迎閱讀我的微信小程式使用者登入token認證: 微信小程式登入服務後端實戰

grpc狀態碼

其實在上一部分內容中我們就已經接觸到了grpc狀態碼

md, ok := metadata.FromIncomingContext(ctx)
        if !ok {
            fmt.Println("get metadata err")
            return nil, status.Error(codes.Unauthenticated, "沒有token認證")  
        }

status.Error(codes.Unauthenticated, "沒有token認證")這既是一個狀態碼

又或者是:

if req.Name == "" {
        return nil, status.Errorf(codes.NotFound, "未找到name:%s", req)
    }

不做過多介紹,關於更多狀態碼

grpc的超時機制

我們在使用grpc呼叫過程中,如果某一個請求沒跟上,或者出現一些情況,導致我們的請求一直沒有返回,這肯定是不行的,例如:我們請求A服務,然後A會去呼叫其他服務,如:A -> B -> C -> D,但是這個過程D一直沒有返回,這肯定會導致我們的C回不來,就處於一直等待的狀態了,這不僅佔用資源,這也使得呼叫者體驗不好,所以需要使用grpc的超時機制來防止此類問題的傳送,而grpc為我們提供了這樣方法,我們只需要只需要在客服端使用:func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

我們以auth認證程式碼為例,只需要在客服端呼叫func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

client:

    //建立一個有時間限定context的上下文
    ctx, _ := context.WithTimeout(context.Background(), 2*time.Second)

    //使用metadata機制和攔截器進行使用者身份驗證
    md := metadata.New(map[string]string{
        "appid":  "ice_moss",
        "appkey": "12345",
    })
    //將md附加到context上下文中
    ctx = metadata.NewOutgoingContext(ctx, md)

在server中我們我們加入:

func (h *HolleService) SayHello(c context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {

    //驗證超時機制
    time.Sleep(5 * time.Second)
    ……
    ……
    ……
}

我們給這個請求2秒的時間,但是我們執行 SayHello服務需要執行5秒時間,顯然這個請求已經超時了,加入超時機制後,他不會一直等待,2秒後他就返回:

2022/05/19 18:38:41 cannot get id for messagerpc error: code = DeadlineExceeded desc = context deadline exceeded

protoc檔案生成的go原始碼解析

我們以metadata機制內容中的proto檔案為例:

syntax = "proto3";

option go_package="/.;proto";
//引入protobuf的內建型別
import "google/protobuf/timestamp.proto";

//定義介面
service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);
}

//列舉型別
enum Gender{
    MALE = 0;
    FE_MALE = 1;
}

message HelloRequest {
    string name = 1;
    string url = 2;
    Gender gender = 3;
    map<string, string> m = 4;  //proto map型別
    google.protobuf.Timestamp addTime = 5;  //protobuf的內建型別
}

message HelloReply {
    string id = 1;
    HelloRequest request = 2;
}

執行protoc -I . holle.proto --go_out=plugins=grpc:.後生成:hello.pb.go檔案

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
//     protoc-gen-go v1.26.0
//     protoc        v3.13.0
// source: md.proto

package proto

import (
    context "context"
    timestamp "github.com/golang/protobuf/ptypes/timestamp"
    grpc "google.golang.org/grpc"
    codes "google.golang.org/grpc/codes"
    status "google.golang.org/grpc/status"
    protoreflect "google.golang.org/protobuf/reflect/protoreflect"
    protoimpl "google.golang.org/protobuf/runtime/protoimpl"
    reflect "reflect"
    sync "sync"
)

const (
    // Verify that this generated code is sufficiently up-to-date.
    _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
    // Verify that runtime/protoimpl is sufficiently up-to-date.
    _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

//列舉型別
type Gender int32

const (
    Gender_MALE    Gender = 0
    Gender_FE_MALE Gender = 1
)

// Enum value maps for Gender.
var (
    Gender_name = map[int32]string{
        0: "MALE",
        1: "FE_MALE",
    }
    Gender_value = map[string]int32{
        "MALE":    0,
        "FE_MALE": 1,
    }
)

func (x Gender) Enum() *Gender {
    p := new(Gender)
    *p = x
    return p
}

func (x Gender) String() string {
    return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}

func (Gender) Descriptor() protoreflect.EnumDescriptor {
    return file_md_proto_enumTypes[0].Descriptor()
}

func (Gender) Type() protoreflect.EnumType {
    return &file_md_proto_enumTypes[0]
}

func (x Gender) Number() protoreflect.EnumNumber {
    return protoreflect.EnumNumber(x)
}

// Deprecated: Use Gender.Descriptor instead.
func (Gender) EnumDescriptor() ([]byte, []int) {
    return file_md_proto_rawDescGZIP(), []int{0}
}

type HelloRequest struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Name    string               `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    Url     string               `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"`
    Gender  Gender               `protobuf:"varint,3,opt,name=gender,proto3,enum=Gender" json:"gender,omitempty"`
    M       map[string]string    `protobuf:"bytes,4,rep,name=m,proto3" json:"m,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` //proto map型別
    AddTime *timestamp.Timestamp `protobuf:"bytes,5,opt,name=addTime,proto3" json:"addTime,omitempty"`                                                                             //protobuf的內建型別
}

func (x *HelloRequest) Reset() {
    *x = HelloRequest{}
    if protoimpl.UnsafeEnabled {
        mi := &file_md_proto_msgTypes[0]
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        ms.StoreMessageInfo(mi)
    }
}

func (x *HelloRequest) String() string {
    return protoimpl.X.MessageStringOf(x)
}

func (*HelloRequest) ProtoMessage() {}

func (x *HelloRequest) ProtoReflect() protoreflect.Message {
    mi := &file_md_proto_msgTypes[0]
    if protoimpl.UnsafeEnabled && x != nil {
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        if ms.LoadMessageInfo() == nil {
            ms.StoreMessageInfo(mi)
        }
        return ms
    }
    return mi.MessageOf(x)
}

// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
func (*HelloRequest) Descriptor() ([]byte, []int) {
    return file_md_proto_rawDescGZIP(), []int{0}
}

func (x *HelloRequest) GetName() string {
    if x != nil {
        return x.Name
    }
    return ""
}

func (x *HelloRequest) GetUrl() string {
    if x != nil {
        return x.Url
    }
    return ""
}

func (x *HelloRequest) GetGender() Gender {
    if x != nil {
        return x.Gender
    }
    return Gender_MALE
}

func (x *HelloRequest) GetM() map[string]string {
    if x != nil {
        return x.M
    }
    return nil
}

func (x *HelloRequest) GetAddTime() *timestamp.Timestamp {
    if x != nil {
        return x.AddTime
    }
    return nil
}

type HelloReply struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Id      string        `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
    Request *HelloRequest `protobuf:"bytes,2,opt,name=request,proto3" json:"request,omitempty"`
}

func (x *HelloReply) Reset() {
    *x = HelloReply{}
    if protoimpl.UnsafeEnabled {
        mi := &file_md_proto_msgTypes[1]
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        ms.StoreMessageInfo(mi)
    }
}

func (x *HelloReply) String() string {
    return protoimpl.X.MessageStringOf(x)
}

func (*HelloReply) ProtoMessage() {}

func (x *HelloReply) ProtoReflect() protoreflect.Message {
    mi := &file_md_proto_msgTypes[1]
    if protoimpl.UnsafeEnabled && x != nil {
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        if ms.LoadMessageInfo() == nil {
            ms.StoreMessageInfo(mi)
        }
        return ms
    }
    return mi.MessageOf(x)
}

// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead.
func (*HelloReply) Descriptor() ([]byte, []int) {
    return file_md_proto_rawDescGZIP(), []int{1}
}

func (x *HelloReply) GetId() string {
    if x != nil {
        return x.Id
    }
    return ""
}

func (x *HelloReply) GetRequest() *HelloRequest {
    if x != nil {
        return x.Request
    }
    return nil
}

var File_md_proto protoreflect.FileDescriptor

var file_md_proto_rawDesc = []byte{
    0x0a, 0x08, 0x6d, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67,
    0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65,
    0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe5, 0x01, 0x0a, 0x0c,
    0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
    0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
    0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75,
    0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01,
    0x28, 0x0e, 0x32, 0x07, 0x2e, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x06, 0x67, 0x65, 0x6e,
    0x64, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x01, 0x6d, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14,
    0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x45,
    0x6e, 0x74, 0x72, 0x79, 0x52, 0x01, 0x6d, 0x12, 0x34, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x54, 0x69,
    0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
    0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
    0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x61, 0x64, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x1a, 0x34, 0x0a,
    0x06, 0x4d, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
    0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
    0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
    0x02, 0x38, 0x01, 0x22, 0x45, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c,
    0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
    0x64, 0x12, 0x27, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01,
    0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
    0x74, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2a, 0x1f, 0x0a, 0x06, 0x47, 0x65,
    0x6e, 0x64, 0x65, 0x72, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x41, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0b,
    0x0a, 0x07, 0x46, 0x45, 0x5f, 0x4d, 0x41, 0x4c, 0x45, 0x10, 0x01, 0x32, 0x31, 0x0a, 0x07, 0x47,
    0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c,
    0x6c, 0x6f, 0x12, 0x0d, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
    0x74, 0x1a, 0x0b, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x0a,
    0x5a, 0x08, 0x2f, 0x2e, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
    0x6f, 0x33,
}

var (
    file_md_proto_rawDescOnce sync.Once
    file_md_proto_rawDescData = file_md_proto_rawDesc
)

func file_md_proto_rawDescGZIP() []byte {
    file_md_proto_rawDescOnce.Do(func() {
        file_md_proto_rawDescData = protoimpl.X.CompressGZIP(file_md_proto_rawDescData)
    })
    return file_md_proto_rawDescData
}

var file_md_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_md_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_md_proto_goTypes = []interface{}{
    (Gender)(0),                 // 0: Gender
    (*HelloRequest)(nil),        // 1: HelloRequest
    (*HelloReply)(nil),          // 2: HelloReply
    nil,                         // 3: HelloRequest.MEntry
    (*timestamp.Timestamp)(nil), // 4: google.protobuf.Timestamp
}
var file_md_proto_depIdxs = []int32{
    0, // 0: HelloRequest.gender:type_name -> Gender
    3, // 1: HelloRequest.m:type_name -> HelloRequest.MEntry
    4, // 2: HelloRequest.addTime:type_name -> google.protobuf.Timestamp
    1, // 3: HelloReply.request:type_name -> HelloRequest
    1, // 4: Greeter.SayHello:input_type -> HelloRequest
    2, // 5: Greeter.SayHello:output_type -> HelloReply
    5, // [5:6] is the sub-list for method output_type
    4, // [4:5] is the sub-list for method input_type
    4, // [4:4] is the sub-list for extension type_name
    4, // [4:4] is the sub-list for extension extendee
    0, // [0:4] is the sub-list for field type_name
}

func init() { file_md_proto_init() }
func file_md_proto_init() {
    if File_md_proto != nil {
        return
    }
    if !protoimpl.UnsafeEnabled {
        file_md_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
            switch v := v.(*HelloRequest); i {
            case 0:
                return &v.state
            case 1:
                return &v.sizeCache
            case 2:
                return &v.unknownFields
            default:
                return nil
            }
        }
        file_md_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
            switch v := v.(*HelloReply); i {
            case 0:
                return &v.state
            case 1:
                return &v.sizeCache
            case 2:
                return &v.unknownFields
            default:
                return nil
            }
        }
    }
    type x struct{}
    out := protoimpl.TypeBuilder{
        File: protoimpl.DescBuilder{
            GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
            RawDescriptor: file_md_proto_rawDesc,
            NumEnums:      1,
            NumMessages:   3,
            NumExtensions: 0,
            NumServices:   1,
        },
        GoTypes:           file_md_proto_goTypes,
        DependencyIndexes: file_md_proto_depIdxs,
        EnumInfos:         file_md_proto_enumTypes,
        MessageInfos:      file_md_proto_msgTypes,
    }.Build()
    File_md_proto = out.File
    file_md_proto_rawDesc = nil
    file_md_proto_goTypes = nil
    file_md_proto_depIdxs = nil
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6

// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GreeterClient interface {
    SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}

type greeterClient struct {
    cc grpc.ClientConnInterface
}

func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
    return &greeterClient{cc}
}

func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
    out := new(HelloReply)
    err := c.cc.Invoke(ctx, "/Greeter/SayHello", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// GreeterServer is the server API for Greeter service.
type GreeterServer interface {
    SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}

// UnimplementedGreeterServer can be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}

func (*UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
    return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}

func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
    s.RegisterService(&_Greeter_serviceDesc, srv)
}

func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    in := new(HelloRequest)
    if err := dec(in); err != nil {
        return nil, err
    }
    if interceptor == nil {
        return srv.(GreeterServer).SayHello(ctx, in)
    }
    info := &grpc.UnaryServerInfo{
        Server:     srv,
        FullMethod: "/Greeter/SayHello",
    }
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
    }
    return interceptor(ctx, in, info, handler)
}

var _Greeter_serviceDesc = grpc.ServiceDesc{
    ServiceName: "Greeter",
    HandlerType: (*GreeterServer)(nil),
    Methods: []grpc.MethodDesc{
        {
            MethodName: "SayHello",
            Handler:    _Greeter_SayHello_Handler,
        },
    },
    Streams:  []grpc.StreamDesc{},
    Metadata: "md.proto",
}

message –> : 生成了對應的結構體

type HelloRequest struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Name    string               `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    Url     string               `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"`
    Gender  Gender               `protobuf:"varint,3,opt,name=gender,proto3,enum=Gender" json:"gender,omitempty"`
    M       map[string]string    `protobuf:"bytes,4,rep,name=m,proto3" json:"m,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` //proto map型別
    AddTime *timestamp.Timestamp `protobuf:"bytes,5,opt,name=addTime,proto3" json:"addTime,omitempty"`                                                                             //protobuf的內建型別
}

我們可以看到這部分程式碼: 這部分為客服端呼叫

//介面
type GreeterClient interface {
    SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}

//介面的實現者
type greeterClient struct {
    cc grpc.ClientConnInterface
}

//構造一個greeterClient物件
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
    return &greeterClient{cc}
}

//實現介面的方法
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
    out := new(HelloReply)
    err := c.cc.Invoke(ctx, "/Greeter/SayHello", in, out, opts...)  
    if err != nil {
        return nil, err
    }
    return out, nil
}

我們在client中要呼叫grpc我們定義的業務程式碼時呼叫了:

func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
    return &greeterClient{cc}
}

入參是: 這個介面中的兩個方法主要就是為我們做序列化和反序列化操作

type ClientConnInterface interface {
    // Invoke performs a unary RPC and returns after the response is received
    // into reply.
    Invoke(ctx context.Context, method string, args interface{}, reply interface{}, opts ...CallOption) error
    // NewStream begins a streaming RPC.
    NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error)
}

然後他返回了一個greeterClient{cc}的物件

在程式碼中可以看到err := c.cc.Invoke(ctx, "/Greeter/SayHello", in, out, opts...)d其中:”/Greeter/SayHello”就是SayHello方法的對映ID

接下來看這部分程式碼:

// GreeterServer is the server API for Greeter service.
type GreeterServer interface {
    SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}

// UnimplementedGreeterServer can be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}

func (*UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
    return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}

func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
    s.RegisterService(&_Greeter_serviceDesc, srv)
}

裡面有一個介面:

type GreeterServer interface {
    SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}

這個介面就需要我們來實現,他就是我們用來做業務邏輯處理的,我們在server中是這樣實現它的:

//介面的實現者
type HelloSerivce struct{}

//實現介面中的方法
func (h *HelloSerivce) SayHello(c context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
    return &proto.HelloReply{
        Id:      "123456789",
        Request: req,
    }, nil
}

然後再來看這個方法:

func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
    s.RegisterService(&_Greeter_serviceDesc, srv)
}

我們在server中是這樣呼叫的:

//建立一個grpc伺服器
s := grpc.NewServer()
proto.RegisterGreeterServer(s, &HelloSerivce{})

我們看這個RegisterGreeterServer(s *grpc.Server, srv GreeterServer)方法入參是s *grpc.Server, srvGreeterServer型別,而GreeterServer是一個介面:

type GreeterServer interface {
    SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}

為什麼我們可以在呼叫的時候直接將&HelloSerivce{}傳入? 答案是:HelloSerivce結構體實現了GreeterServer介面,這個&HelloSerivce{}物件可以直接傳入。 如果你對介面的知識不理解也可以閱讀「Golang成長之路」面向介面

其實我們在hello.pb.go的原始碼中就需要理解這麼多就行了。

這裡我們的GRPC的內容就介紹結束,感謝閱讀!

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章