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)