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-go 和 protoc-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 开的流程
- 创建存根文件及方法定义,然后编译
// 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;
}
- 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)
}
}
- 客户端调用实现
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 方式(流模式)
- 简单模式(simple RPC)
- 服务端的数据流
- 客户端的数据流
- 双向的
集中学习 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)。
注意事项
- 对于标量消息字段:一旦消息被解析后,无法区分字段是否被设置为默认值(例如布尔值是否为
false)还是字段根本未被设置。 - 布尔值的使用建议:避免定义布尔值的默认值为
false,因为可能会导致触发无意的行为。 - 标量消息的序列化:如果一个标量消息字段被设置为默认值,该值不会被序列化传输。
可以通过参考 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 的状态码
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