Go工程师体系课 003【学习笔记】

grpc

  • grpc
  • grpc-go

grpc 无缝集成了 protobuf

protobuf

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

protoc 的下载

下载 protoc 和go get github.com/golang/protobuf/protoc-gen-go

syntax = "proto3";

option go_package = "."; //这名不写现在编译不了

message HelloRequest {
  string name = 1; //1 是编号不是值
}
protoc --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  ./helloworld.proto

会在目录中生成一个helloworld.pb.go的文件

压缩比的对比

package main

import (
 "RpcLearn/protobuf"
 "encoding/json"
 "fmt"
 "github.com/golang/protobuf/proto"
)

// TIP <p>To run your code, right-click the code and select <b>Run</b>.</p> <p>Alternatively, click
// the <icon src="AllIcons.Actions.Execute"/> icon in the gutter and select the <b>Run</b> menu item from here.</p>
type Hello struct {
 Name string `json:"name"`
}

func main() {
 //TIP <p>Press <shortcut actionId="ShowIntentionActions"/> when your caret is at the underlined text
 // to see how GoLand suggests fixing the warning.</p><p>Alternatively, if available, click the lightbulb to view possible fixes.</p>
 //s := "gopher"
 //fmt.Printf("Hello and welcome, %s!\n", s)
 //
 //for i := 1; i <= 5; i++ {
 // //TIP <p>To start your debugging session, right-click your code in the editor and select the Debug option.</p> <p>We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
 // // for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.</p>
 // fmt.Println("i =", 100/i)
 //}
 jsonStruct := Hello{
  Name: "gopher",
 }
 jsonReq, _ := json.Marshal(jsonStruct)
 fmt.Println(string(jsonReq))
 fmt.Println(len(jsonReq))
 req := __.HelloRequest{
  Name: "gopher",
 }
 rsp, _ := proto.Marshal(&req)
 fmt.Println(rsp)
 fmt.Println(len(rsp))

}

差不多两倍的差异

Go 语言中使用 Protobuf 的变化说明

教程内容 当前实际
以前只有一个 .pb.go 文件 现在生成两个文件:一个 .pb.go(数据结构),一个 _grpc.pb.go(gRPC 接口)
插件不分离 插件已拆分为 protoc-gen-goprotoc-gen-go-grpc,需要分别安装和调用

📌 当前行为说明

从 Go 的 Protobuf 插件 v1.20+ 开始,gRPC 支持被拆分为单独的插件 protoc-gen-go-grpc,用于生成服务接口相关代码。这种方式更模块化,职责更清晰。

因此,执行如下命令:

protoc --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  ./your_file.proto

将会生成:

  • your_file.pb.go: 包含消息结构(如 Request、Response)
  • your_file_grpc.pb.go: 包含服务接口定义(如 YourServiceServer)

建议保留并同步这两个文件。

简单梳理一下 grpc 开的流程

  1. 创建存根文件及方法定义,然后编译
// proto/helloworld.proto
syntax = "proto3";
option go_package = ".;proto";

service Greeter{
    rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
    string name = 1;
}
message HelloReply {
    string message = 1;
}
  1. server 端实现定义方法的调用,注意 server 实现(鸭子类型)
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "google.golang.org/grpc"
 "log"
 "net"
)

type Server struct {
 proto.UnimplementedGreeterServer // 👈 重点:嵌入这个类型
}

func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
 reply := &proto.HelloReply{
  Message: "Hello " + req.Name,
 }
 return reply, nil
}

func main() {
 g := grpc.NewServer()                     // 创建一个新的gRPC服务器
 proto.RegisterGreeterServer(g, &Server{}) // 注册服务
 listener, err := net.Listen("tcp", ":9090")
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {
  log.Fatalf("failed to serve: %v", err)
 }
}
  1. 客户端调用实现
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
)

func main() {
 // Create a new gRPC server
 //conn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
 // 使用安全连接
 //creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})
 conn, err := grpc.NewClient("localhost:9090",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
 )
 if err != nil {
  panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "world"})
 if err != nil {
  panic(err)
 }
 fmt.Println(r.Message)

}

grpc 的 stream 方式(流模式)

  1. 简单模式(simple RPC)
  2. 服务端的数据流
  3. 客户端的数据流
  4. 双向的

集中学习 grpc

Protobuf 类型 Go 类型
double float64
float float32
int32 int32
int64 int64
uint32 uint32
uint64 uint64
sint32 int32
sint64 int64
fixed32 uint32
fixed64 uint64

默认值

默认值说明

在 Protobuf 中,不同字段类型有以下默认值:

  • 字符串类型(string):默认值是一个空字符串。
  • 字节类型(bytes):默认值是一个空的字节序列。
  • 布尔类型(bool):默认值是 false
  • 数值类型(数字类型):默认值为 0
  • 枚举类型:默认值是第一个定义的枚举值,且必须是 0
  • 消息类型(message):默认值是未设置(即 null)。

注意事项

  1. 对于标量消息字段:一旦消息被解析后,无法区分字段是否被设置为默认值(例如布尔值是否为 false)还是字段根本未被设置。
  2. 布尔值的使用建议:避免定义布尔值的默认值为 false,因为可能会导致触发无意的行为。
  3. 标量消息的序列化:如果一个标量消息字段被设置为默认值,该值不会被序列化传输。

可以通过参考 Generated Code Guide 来更详细地了解 Protobuf 默认值的处理方式。


枚举类型的定义

在需要为消息类型的某个字段指定一组“预定义值序列”时,可以使用枚举类型。每个枚举值都对应一个整数值。

示例

下面是一个枚举的定义示例:

message SearchRequest {
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 1;
}
  • 服务端数据流(获取实时数据)
  • 客户端数据流(上报数据)
  • 双向数据流
 syntax = "proto3";

option go_package = ".;proto";

service Greeter {
    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 message = 1;
}

编译生成 go 的 protobuf

protoc --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  ./helloworld.proto

服务端代码

package main

import (
 "RpcLearn/stream_grpc_test/proto"
 "fmt"
 "google.golang.org/grpc"
 "log"
 "net"
 "sync"
 "time"
)

const PORT = ":50052"

type server struct {
 proto.UnimplementedGreeterServer // 👈 重点:嵌入这个类型
}

func (s server) GetStream(data *proto.StreamReqData, res proto.Greeter_GetStreamServer) error {
 //TODO implement me
 //panic("implement me")
 i := 0
 for {
  i++
  _ = res.Send(&proto.StreamResData{
   Message: fmt.Sprintf("%v", time.Now().Unix()),
  })
  time.Sleep(time.Second)
  if i >= 10 {
   break
  }
 }
 return nil
}

func (s server) PutStream(cliStr proto.Greeter_PutStreamServer) error {
 //TODO implement me
 //panic("implement me")//
 for {
  if a, err := cliStr.Recv(); err != nil {
   fmt.Println(err)
   break
  } else {
   fmt.Println(a)
  }

 }
 return nil
}

func (s server) AllStream(allStr proto.Greeter_AllStreamServer) error {
 //TODO implement me
 //panic("implement me")
 wg := sync.WaitGroup{}
 wg.Add(2)
 go func() {
  defer wg.Done()
  for {
   if a, err := allStr.Recv(); err != nil {
    fmt.Println(err)
    break
   } else {
    fmt.Println("客户端发送的消息:", a)
   }
  }

 }()
 go func() {
  defer wg.Done()
  for {
   allStr.Send(&proto.StreamResData{
    Message: fmt.Sprintf("我是服务端数据:%v\n", time.Now().Unix()),
   })
   time.Sleep(time.Second)
  }

 }()
 wg.Wait()
 return nil
}

func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if (!ok) {
        // handle missing metadata
    }
    // do something with metadata
    return &pb.SomeResponse{}, nil
}

func main() {
 // 创建一个新的gRPC服务器
 g := grpc.NewServer()
 proto.RegisterGreeterServer(g, &server{}) // 注册服务
 listener, err := net.Listen("tcp", PORT)
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {
  log.Fatalf("failed to serve: %v", err)
 }
}

客户端代码

package main

import (
 "RpcLearn/stream_grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
 "sync"
 "time"
)

func main() {
 // Create a new gRPC server
 conn, err := grpc.("localhost:50052",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
 )
 if err != nil {
  panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 //res, _ := c.GetStream(context.Background(), &proto.StreamReqData{Data: "慕课网"})
 //for {
 // r, err := res.Recv()
 // if err != nil {
 //  break
 // }
 // fmt.Println(r)
 //}
 //// 客户端流模式
 //putS, _ := c.PutStream(context.Background())
 //i := 0
 //for {
 // i++
 // putS.Send(&proto.StreamReqData{Data: fmt.Sprintf("慕课网%v", i)})
 // time.Sleep(time.Second)
 // if i >= 10 {
 //  break
 // }
 //}

 //双向流模式
 allStr, _ := c.AllStream(context.Background())
 wg := sync.WaitGroup{}
 wg.Add(2)
 go func() {
  defer wg.Done()
  for {
   if a, err := allStr.Recv(); err != nil {
    fmt.Println(err)
    break
   } else {
    fmt.Println("我是客户端:%v", a)
   }
  }
 }()
 go func() {
  defer wg.Done()
  i := 0
  for {
   i++
   allStr.Send(&proto.StreamReqData{Data: fmt.Sprintf("双向的慕课网:%v", i)})
   time.Sleep(time.Second)
   if i >= 10 {
    break
   }
  }
 }()
 wg.Wait()
}

Protobuf 类型与 Go 类型对应关系(含编码建议)

.proto Type 描述说明 Go Type
double 浮点类型 float64
float 浮点类型 float32
int32 使用变长编码,对负值效率低;如有可能为负数,建议用 sint32 替代 int32
int64 使用变长编码 int64
uint32 使用变长编码 uint32
uint64 使用变长编码 uint64
sint32 使用变长编码,对负值更高效(比 uint32 更好) int32
sint64 使用变长编码,对负值更高效 int64
fixed32 始终占 4 字节,适用于总是比 2²²⁸ 大的值,比 uint32 更高效 uint32
fixed64 始终占 8 字节,适用于总是比 2²⁵⁶ 大的值,比 uint64 更高效 uint64
sfixed32 始终占 4 字节 int32
sfixed64 始终占 8 字节 int64
bool 布尔值 bool
string 必须是 UTF-8 或 ASCII 编码的文本 string
bytes 可包含任意顺序的字节数据 []byte

option go_package

编译生成目录的指定

如何在一个 proto 中引入其它 proto

import "base.proto"

import "google/protobuf/empty.proto"

嵌套的 message

枚举类型

enum Gender{
  MALE = 0;
  FEMALE = 1;
}

map 类型 map<string,string> mp = 4

如何使用时间戳 google/protobuf/timestamp.proto

message HelloRequest {
  string name = 1; // 姓名 相当于文档
  string url = 2;
  Gender g = 3;
  map<string, string> mp = 4;
  google.protobuf.Timestamp addTime = 5;
}

grpc 的更多功能

gRPC 让我们可以像本地调用一样实现远程调用,对于每一次的 RPC 调用中,都可能会有一些有用的数据,而这些数据就可以通过 metadata 来传递。metadata 是以 key-value 的形式存储数据的,其中 key 是 string 类型,而 value 是[]string,即一个字符串数组类型。metadata 使得 client 和 server 能够为对方提供关于本次调用的一些信息,就像一次 http 请求的 RequestHeader 和 ResponseHeader 一样。http 中 header 的生命周期是一 http 请求,那么 metadata 的生命周期就是一次 RPC 调用。

metadata

不是直接侵入到业务代码中的

// 新建
type MD mp[string][]string
// 创建
// 第一种方式
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})

// 第二种方式 key 不区分大小写,会被统一转成小写。
md := metadata.Pairs(
    "key1", "val1",
    "key1", "val1-2", // "key1" will have map value []string{"val1", "val1-2"}
    "key2", "val2",
)

发送 metadata

md := metadata.Pairs("key", "val")

// 新建一个有 metadata 的 context
ctx := metadata.NewOutgoingContext(context.Background(), md)

// 单向 RPC
response, err := client.SomeRPC(ctx, someRequest)

接收 metadata

func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        // handle missing metadata
    }
    // do something with metadata
    return &pb.SomeResponse{}, nil
}

grpc 的拦截器

服务端和客户端的拦截器

package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "google.golang.org/grpc"
 "log"
 "net"
)

type Server struct {
 proto.UnimplementedGreeterServer // 👈 重点:嵌入这个类型
}

func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
 reply := &proto.HelloReply{
  Message: "Hello " + req.Name,
 }
 return reply, nil
}

func main() {
 interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  log.Println("Request:接收到一个新的请求", req)
  resp, err = handler(ctx, req)
  if err != nil {
   log.Println("Error:", err)
  }
  log.Println("Response:处理这个新请求完成", resp)
  return resp, err
 }
 opt := grpc.UnaryInterceptor(interceptor)
 g := grpc.NewServer(opt)                  // 创建一个新的gRPC服务器
 proto.RegisterGreeterServer(g, &Server{}) // 注册服务
 listener, err := net.Listen("tcp", ":9090")
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {
  log.Fatalf("failed to serve: %v", err)
 }
}
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
 "time"
)

func main() {
 // Create a new gRPC server
 //conn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
 // 使用安全连接
 //creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})
 // 添加拦截器
 clientInterceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  start := time.Now()
  err := invoker(ctx, method, req, reply, cc, opts...)
  fmt.Println("耗时", time.Since(start), err)
  return err
 }
 opt := grpc.WithUnaryInterceptor(clientInterceptor)
 conn, err := grpc.NewClient("localhost:9090",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
  opt,
 )
 if err != nil {
  panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "world"})
 if err != nil {
  panic(err)
 }
 fmt.Println(r.Message)

}

grpc 通过 metadata 来验证登录

// server
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "google.golang.org/grpc"
 "google.golang.org/grpc/codes"
 "google.golang.org/grpc/metadata"
 "google.golang.org/grpc/status"
 "log"
 "net"
)

type Server struct {
 proto.UnimplementedGreeterServer // 👈 重点:嵌入这个类型
}

func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
 reply := &proto.HelloReply{
  Message: "Hello " + req.Name,
 }
 return reply, nil
}

func main() {
 interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  log.Println("Request:接收到一个新的请求", req)
  md, ok := metadata.FromIncomingContext(ctx)
  if !ok {
   log.Println("没有metadata")
   return resp, status.Error(codes.Unauthenticated, "没有metadata")
  }

  var (
   appid  string
   appkey string
  )
  if val1, ok := md["appid"]; ok {
   appid = val1[0]
  }
  if val2, ok := md["appkey"]; ok {
   appkey = val2[0]
  }
  if appid != "1010101" || appkey != "123456" {
   return resp, status.Error(codes.Unauthenticated, "无token认证信息")
  }

  resp, err = handler(ctx, req)
  if err != nil {
   log.Println("Error:", err)
  }
  log.Println("Response:处理这个新请求完成", resp)
  return resp, err
 }
 opt := grpc.UnaryInterceptor(interceptor)
 g := grpc.NewServer(opt)                  // 创建一个新的gRPC服务器
 proto.RegisterGreeterServer(g, &Server{}) // 注册服务
 listener, err := net.Listen("tcp", ":9090")
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {
  log.Fatalf("failed to serve: %v", err)
 }
}
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
)

type customCredentials struct {
}

func (c *customCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
 return map[string]string{
  "appid":  "1010101",
  "appkey": "123456",
 }, nil
}
func (c *customCredentials) RequireTransportSecurity() bool {
 return false
}

func main() {

 // 添加拦截器
 //clientInterceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
 // start := time.Now()
 // //生成metadata
 // md := metadata.New(map[string]string{
 //  "appid":  "1010101",
 //  "appkey": "123456",
 // })
 // ctx = metadata.NewOutgoingContext(ctx, md)
 // err := invoker(ctx, method, req, reply, cc, opts...)
 // fmt.Println("耗时", time.Since(start), err)
 // return err
 //}
 //opt := grpc.WithUnaryInterceptor(clientInterceptor
 // 另一种方式
 opt := grpc.WithPerRPCCredentials(&customCredentials{}) // 有一个专门的metadata的拦截器
 conn, err := grpc.NewClient("localhost:9090",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
  opt,
 )
 if err != nil {
  panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "world"})
 if err != nil {
  panic(err)
 }
 fmt.Println(r.Message)

}

grpc(protoc-gen-validate)

protoc-gen-validate(简称 PGV)是一个 Protocol Buffers 插件,用于在生成的 Go 代码中添加结构体字段的验证逻辑。

它通过在 .proto 文件中添加 validate 规则,自动为每个字段生成验证代码,避免你手动写验证逻辑。

syntax = "proto3";

import "validate/validate.proto";

message User {
  string name = 1 [(validate.rules).string.min_len = 3];
  int32 age = 2 [(validate.rules).int32.gte = 18];
}

查看protoc-gen-validate的简介

grpc_proto_validate

syntax = "proto3";

import "validate.proto";

option go_package = ".;proto";


service Greeter {
  rpc SayHello (Person) returns (Person);
}

message Person {
  uint64 id = 1 [(validate.rules).uint64.gt = 999];

  string email = 2 [(validate.rules).string.email = true];

  string mobile = 3 [
    (validate.rules).string = {
      pattern: "^1[3-9]\\d{9}$"
    }
  ];
}

编译这个 proto 添加一个--validate_out="lang=go" ,先要安装go install github.com/bufbuild/protoc-gen-validate@latest

protoc \
  --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  --validate_out=lang=go:. \
  ./helloworld.proto
package main

import (
 "RpcLearn/grpc_validate_test/proto"
 "context"
 "google.golang.org/grpc"
 "google.golang.org/grpc/codes"
 "google.golang.org/grpc/status"
 "log"
 "net"
)

type Server struct {
 proto.UnimplementedGreeterServer // 👈 重点:嵌入这个类型
}

func (s *Server) SayHello(ctx context.Context, request *proto.Person) (*proto.Person, error) {
 // Simulate some processing
 return &proto.Person{
  Id:     request.Id,
  Mobile: request.Mobile,
  Email:  request.Email,
 }, nil
}

type Validator interface {
 Validate() error
}

func main() {
 //p := new(proto.Person)
 //err := p.Validate()
 //if err != nil {
 // panic(err)
 //}
 var interceptor grpc.UnaryServerInterceptor
 interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  if v, ok := req.(Validator); ok {
   if err := v.Validate(); err != nil {
    return nil, status.Error(codes.InvalidArgument, err.Error())
   }
  }
  return handler(ctx, req)
 }
 opt := grpc.UnaryInterceptor(interceptor)
 g := grpc.NewServer(opt)                  // 创建一个新的gRPC服务器
 proto.RegisterGreeterServer(g, &Server{}) // 注册服务
 listener, err := net.Listen("tcp", ":50051")
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {
  log.Fatalf("failed to serve: %v", err)
 }
}
package main

import (
 "RpcLearn/grpc_validate_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
 "time"
)

type customCredential struct{}

func main() {
 // 拦截器(可选)
 clientInterceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  start := time.Now()
  err := invoker(ctx, method, req, reply, cc, opts...)
  fmt.Println("调用耗时:", time.Since(start), "错误:", err)
  return err
 }

 // 使用新方式连接(推荐)
 conn, err := grpc.NewClient("localhost:50051",
  grpc.WithTransportCredentials(insecure.NewCredentials()), // 替代 grpc.WithInsecure()
  grpc.WithUnaryInterceptor(clientInterceptor),
 )
 if err != nil {
  panic(err)
 }
 defer conn.Close()

 // 构建 gRPC 客户端
 c := proto.NewGreeterClient(conn)

 // 调用 RPC
 rsp, err := c.SayHello(context.Background(), &proto.Person{
  Id:     1000,
  Email:  "bobby@test.com",
  Mobile: "13222223333",
 })
 if err != nil {
  panic(err)
 }

 fmt.Println("返回 ID:", rsp.Id)
}

grpc 中的异常处理

1. grpc 的状态码

grpc 状态码文档


go 的异常处理

1. 服务端

st := status.New(codes.InvalidArgument, "invalid username")

2. 客户端

st, ok := status.FromError(err)
if !ok {
    // Error was not a status error
}
st.Message()
st.Code()

超时机制

ctx,_ = content.WithTimeout(context.Background(),time.Second*3)

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://joyjs.cn/archives/4772

(0)
Walker的头像Walker
上一篇 2025年11月25日 03:00
下一篇 2025年11月25日 01:00

相关推荐

  • 深入理解ES6 013【学习笔记】

    用模块封装代码 javascript用“共享一切”的方法加载代码,这是该语言中最容易出错且另人感到困惑的地方。其他语言使用诸如包这样的概念来定义代码作用域。在es6以前,在应用程序的每一个js中定义的一切都共享一个全局作用域。随着web应用程序变得更加复杂,js代码的使用量也开始增长,这一做法会引起问题,如命名冲突和安全问题。es6的一个目标是解决作用域问题…

    个人 2025年3月8日
    97700
  • TS珠峰 002【学习笔记】

    泛型 /* * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git &a…

    个人 2025年3月27日
    1.3K00
  • Go工程师体系课 001【学习笔记】

    转型 想在短时间系统转到Go工程理由 提高CRUD,无自研框架经验 拔高技术深度,做专、做精需求的同学 进阶工程化,拥有良好开发规范和管理能力的 工程化的重要性 高级开的期望 良好的代码规范 深入底层原理 熟悉架构 熟悉k8s的基础架构 扩展知识广度,知识的深度,规范的开发体系 四个大的阶段 go语言基础 微服务开发的(电商项目实战) 自研微服务 自研然后重…

    个人 2025年11月25日
    5500
  • 无畏前行,拳释力量 🥊💪

    拼搏,是一种态度 生活就像一场比赛,没有捷径可走,只有不断训练、突破、超越,才能站上属于自己的舞台。这不仅是一种对抗,更是一种自我的觉醒——敢于迎战,敢于挑战,敢于成为更强的自己。 运动中的拼搏精神 无论是拳击、跑步,还是力量训练,每一次出拳、每一次挥汗、每一次咬牙坚持,都是对身体与心灵的磨炼。拼搏不是单纯的对抗,而是一种态度——面对挑战,不退缩;面对失败,…

    个人 2025年2月26日
    1.1K00
  • Go工程师体系课 005【学习笔记】

    微服务开发 创建一个微服务项目,所有的项目微服务都在这个项目中进行,创建joyshop_srv,我们无创建用户登录注册服务,所以我们在项目目录下再创建一个目录user_srv 及user_srv/global(全局的对象新建和初始化)user_srv/handler(业务逻辑代码)user_srv/model(用户相关的 model)user_srv/pro…

    个人 2025年11月25日
    4500

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
欢迎🌹 Coding never stops, keep learning! 💡💻 光临🌹