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://walker-learn.xyz/archives/4772