gRPC
安装
方法1:
go get -u -v google.golang.org/grpc
方法2:
git clone https://github.com/grpc/grpc-go.git $GOPATH/pkg/mod/google.golang.org/grpc
git clone https://github.com/golang/net.git $GOPATH/pkg/mod/google.golang.org/x/net
git clone https://github.com/golang/text.git $GOPATH/pkg/mod/google.golang.org/x/text
git clone https://github.com/google/go-genproto.git $GOPATH/pkg/mod/google.golang.org/genproto
cd $GOPATH/pkg/mod
go install google.golang.org/grpc
模式
一元模式
(Simple RPC)
客户端发起一次请求,服务端响应一次数据
服务端
//protobuf定义的服务函数
type OutMsg struct{}
func (om *OutMsg) SayHello(ctx context.Context, p *pb.Person) (*pb.PhoneNumber, error) {
p.Age = 18
p.Name = "wang"
var pn pb.PhoneNumber = pb.PhoneNumber{Number: "11", Type: 2}
fmt.Println("rotem call")
return &pn, nil
}
/**
* @func: CreateGrpcSer
* @msg: 创建grpc服务端
* @param {*}
* @return {*}
*/
func CreateGrpcSer() {
//初始化对象
grpcSer := gRPC.NewServer()
//注册服务
pb.RegisterHelloServer(grpcSer, new(OutMsg))
//设置监听,ip,port
listener, err := net.Listen("tcp", "0.0.0.0:38000")
if err != nil {
log.Fatal(err)
return
}
defer listener.Close()
//启动
grpcSer.Serve(listener)
}
客户端
/**
* @func: CreateGrpcCli
* @msg: 创建grpc客户端
* @param {*}
* @return {*}
*/
func CreateGrpcCli() {
//连接grpc服务
grpcConn, err := gRPC.Dial("192.168.11.140:38000", gRPC.WithInsecure()) // gRPC.WithInsecure()以安全的方式操作
if err != nil {
log.Fatal(err)
return
}
defer grpcConn.Close()
//初始化grpc客户端
grpcClient := pb.NewHelloClient(grpcConn)
//调用远程服务
pNumber, err := grpcClient.SayHello(context.TODO(), &pb.Person{})
if err != nil {
log.Fatal(err)
return
}
fmt.Println(pNumber)
}
stream模式
流模式可以源源不断的推送数据,很适合传输一些大数据,或服务端和客户端长时间数据交互。
服务端数据流模式(Server-side streaming RPC)
客户端发起一次请求,服务端返回一段连续的数据流。
结束传输服务端会传送EOF
客户端数据流模式(Client-side streaming RPC)
与服务端数据流模式相反,客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应
双向数据流模式(Bidirectional streamin RPC)
客户端和服务端都可以同时向对方发送数据流
关键字 stream
stream.proto
syntax="proto3";
option go_package="../;pb";
//关键字 stream
service Greeter{
rpc GetStream(StreamReqData) returns (stream StreamResData){} // 服务端流模式
rpc PostStream(stream StreamReqData) returns (StreamResData); //客户端流模式
rpc AllStream(stream StreamReqData)returns(stream StreamResData)//双向流模式
}
message StreamReqData{
string data=1;
}
message StreamResData{
string data=1;
}
server.go
package main
import (
"fmt"
"grpcStream/pb"
"log"
"net"
"sync"
"time"
"google.golang.org/grpc"
)
const PORT = ":50052"
/*注意 服务端接口函数定义
// GreeterServer is the server API for Greeter service.
type GreeterServer interface {
GetStream(*StreamReqData, Greeter_GetStreamServer) error
PostStream(Greeter_PostStreamServer) error
AllStream(Greeter_AllStreamServer) error
}
*/
type Serv struct{}
/**
* @func:
* @msg: 向客户端推送数据流
* @param {*pb.StreamReqData} req
* @param {pb.Greeter_GetStreamServer} res
* @return {error}
*/
func (s *Serv) GetStream(req *pb.StreamReqData, res pb.Greeter_GetStreamServer) (err error) {
fmt.Println(req.Data)
for i := 0; i < 10; i++ {
err := res.Send(&pb.StreamResData{
Data: fmt.Sprintf("%v,%d", time.Now().Unix(), i),
})
if err != nil {
log.Fatalln(err)
break
}
// time.Sleep(time.Second)
}
return err
}
/**
* @func:
* @msg:接收客户端推送数据
* @param {pb.Greeter_PostStreamServer} cliStr
* @return {*}
*/
func (s *Serv) PostStream(cliStr pb.Greeter_PostStreamServer) (err error) {
for {
if a, err := cliStr.Recv(); err != nil {
fmt.Printf("err:%v", err)
break
} else {
fmt.Println(a.Data)
}
}
return err
}
/**
* @func:
* @msg: 双向推流服务端
* @param {pb.Greeter_AllStreamServer} allStr
* @return {*}
*/
func (s *Serv) AllStream(allStr pb.Greeter_AllStreamServer) (err error) {
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
for {
if a, err := allStr.Recv(); err != nil {
fmt.Printf("err:%v", err)
break
} else {
fmt.Println(a.Data)
}
}
}()
go func() {
defer wg.Done()
for {
err = allStr.Send(&pb.StreamResData{
Data: fmt.Sprintf("server_send:%v", time.Now().Unix()),
})
if err != nil {
fmt.Printf("err:%v", err)
break
}
time.Sleep(time.Second)
}
}()
defer wg.Wait()
return err
}
func main() {
lis, err := net.Listen("tcp", PORT)
if err != nil {
panic(err)
}
defer lis.Close()
s := grpc.NewServer()
pb.RegisterGreeterServer(s, new(Serv))
err = s.Serve(lis)
if err != nil {
panic(err)
}
}
client.go
package main
import (
"context"
"fmt"
"grpcStream/pb"
"log"
"sync"
"time"
"google.golang.org/grpc"
)
/**
* @func: GetGrpcCli
* @msg: 创建grpc客户端-服务端流模式
* @param {*}
* @return {*}
*/
func GetGrpcCli() error {
//连接grpc服务
grpcConn, err := grpc.Dial("192.168.11.140:50052", grpc.WithInsecure()) // gRPC.WithInsecure()以安全的方式操作
if err != nil {
log.Fatal(err)
panic(err)
}
defer grpcConn.Close()
//初始化grpc客户端
grpcClient := pb.NewGreeterClient(grpcConn)
//调用远程服务
res, err := grpcClient.GetStream(context.TODO(), &pb.StreamReqData{Data: "req"})
if err != nil {
log.Fatal(err)
panic(err)
}
for {
a, err := res.Recv()
if err != nil {
fmt.Println(err)
break
}
fmt.Println(a)
}
panic(err)
}
/**
* @func:
* @msg: 创建grpc客户端-客户端流模式
* @param {*}
* @return {*}
*/
func PostGrpcCli() error {
//连接grpc服务
grpcConn, err := grpc.Dial("192.168.11.140:50052", grpc.WithInsecure()) // gRPC.WithInsecure()以安全的方式操作
if err != nil {
log.Fatal(err)
return err
}
defer grpcConn.Close()
//初始化grpc客户端
grpcClient := pb.NewGreeterClient(grpcConn)
//调用远程服务
postS, err := grpcClient.PostStream(context.TODO())
if err != nil {
log.Fatal(err)
return err
}
for i := 0; i < 5; i++ {
err = postS.Send(&pb.StreamReqData{
Data: fmt.Sprintf("%v,%d", time.Now().Unix(), i),
})
if err != nil {
log.Fatalln(err)
break
}
time.Sleep(time.Second)
}
return err
}
/**
* @func:
* @msg: 双向推流客户端
* @param {*}
* @return {*}
*/
func AllGrpcCli() error {
grpcConn, err := grpc.Dial("192.168.11.140:50052", grpc.WithInsecure()) // gRPC.WithInsecure()以安全的方式操作
if err != nil {
log.Fatal(err)
return err
}
defer grpcConn.Close()
//初始化grpc客户端
grpcClient := pb.NewGreeterClient(grpcConn)
//调用远程服务
allStr, err := grpcClient.AllStream(context.Background())
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
for {
if a, err := allStr.Recv(); err != nil {
fmt.Printf("err:%v", err)
break
} else {
fmt.Println(a.Data)
}
}
}()
go func() {
defer wg.Done()
for {
err = allStr.Send(&pb.StreamReqData{
Data: fmt.Sprintf("client_send:%v", time.Now().Unix()),
})
if err != nil {
fmt.Printf("err:%v", err)
break
}
time.Sleep(time.Second)
}
}()
defer wg.Wait()
return err
}
func main() {
GetGrpcCli()
PostGrpcCli()
AllGrpcCli()
}
metadata
metadata.proto
syntax = "proto3";
option go_package = "./pb;metadata";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest{
string name = 1;
}
message HelloReply {
string message = 1;
}
server.go
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
mdata "grpctest/metadata/pb"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
var host = "127.0.0.1"
var (
ServiceName = flag.String("ServiceName", "hello_service", "service name")
Port = flag.Int("Port", 50001, "listening port")
)
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", *Port))
if err != nil {
log.Fatalf("failed to listen:%s", err)
} else {
fmt.Printf("listen at:%d\n", *Port)
}
defer lis.Close()
s := grpc.NewServer()
defer s.GracefulStop()
mdata.RegisterGreeterServer(s, new(server))
addr := fmt.Sprintf("%s,%d", host, *Port)
fmt.Printf("server add:%s\n", addr)
s.Serve(lis)
}
type server struct{}
func (s *server) SayHello(ctx context.Context, in *mdata.HelloRequest) (*mdata.HelloReply, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
fmt.Printf("get metadata error")
}
if t, ok := md["timestamp"]; ok {
fmt.Printf("timestamp from metadata:\n")
for i, e := range t {
fmt.Printf("%d. %s\n", i, e)
}
}
return &mdata.HelloReply{Message: "hello" + in.Name}, nil
}
client.go
package main
import (
"context"
"fmt"
mdata "grpctest/metadata/pb"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
const (
timestampFormat = time.StampNano // "Jan _2 xx:xx:xx.000"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:50001", grpc.WithInsecure())
if err != nil {
panic(err)
}
client := mdata.NewGreeterClient(conn)
md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
ctx := metadata.NewOutgoingContext(context.Background(), md)
resp, err := client.SayHello(ctx, &mdata.HelloRequest{Name: "hello"})
if err != nil {
fmt.Printf("call serve error:%s\n", err)
} else {
fmt.Printf("Reply is %s\n", resp.Message)
}
}
拦截器
1. 一元模式
interceptor.proto
syntax = "proto3";
option go_package = ".;proto";
package proto;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
server.go
package main
import (
"context"
"fmt"
"net"
"time"
"google.golang.org/grpc"
"grpctest/grpc_interpretor/proto"
)
type Server struct{}
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
error) {
time.Sleep(2 * time.Second)
return &proto.HelloReply{
Message: "hello " + request.Name,
}, nil
}
func main() {
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
fmt.Println("接收到了一个新的请求")
res, err := handler(ctx, req)
fmt.Println("请求已经完成")
return res, err
}
opt := grpc.UnaryInterceptor(interceptor)
g := grpc.NewServer(opt)
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}
client.go
package main
import (
"context"
"fmt"
"grpctest/grpc_interpretor/proto"
"time"
"google.golang.org/grpc"
)
func main() {
interceptor := 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.Printf("耗时:%s\n", time.Since(start))
return err
}
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
conn, err := grpc.Dial("127.0.0.1:50051", opts...)
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
2. stream模式
go-grpc-middleware
https://github.com/grpc-ecosystem/go-grpc-middleware
client-retry.go
package main
import (
"context"
"fmt"
"time"
"google.golang.org/grpc/codes"
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
"google.golang.org/grpc"
"grpctest/grpc_interpretor/proto"
)
func main() {
interceptor := 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.Printf("耗时:%s\n", time.Since(start))
return err
}
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
retryOpts := []grpc_retry.CallOption{
grpc_retry.WithMax(3),
grpc_retry.WithPerRetryTimeout(1 * time.Second),
grpc_retry.WithCodes(codes.Unknown, codes.DeadlineExceeded, codes.Unavailable),
}
opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
//这个请求应该多长时间超时, 这个重试应该几次、当服务器返回什么状态码的时候重试
opts = append(opts, grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOpts...)))
conn, err := grpc.Dial("127.0.0.1:50051", opts...)
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
token_auth
server.go
package main
import (
"context"
"fmt"
"net"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/grpc"
"grpctest/grpc_token_auth_test/proto"
)
type Server struct{}
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
error) {
return &proto.HelloReply{
Message: "hello " + request.Name,
}, nil
}
func main() {
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
fmt.Println("接收到了一个新的请求")
md, ok := metadata.FromIncomingContext(ctx)
fmt.Println(md)
if !ok {
//grpc的错误处理了
return resp, status.Error(codes.Unauthenticated, "无token认证信息")
}
var (
appid string
appkey string
)
if va1, ok := md["appid"]; ok {
appid = va1[0]
}
if va1, ok := md["appkey"]; ok {
appkey = va1[0]
}
if appid != "101010" || appkey != "i am key" {
return resp, status.Error(codes.Unauthenticated, "无token认证信息")
}
res, err := handler(ctx, req)
fmt.Println("请求已经完成")
return res, err
}
opt := grpc.UnaryInterceptor(interceptor)
g := grpc.NewServer(opt)
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}
拦截器+metadata
client.go
package main
import (
"context"
"fmt"
"grpctest/grpc_interpretor/proto"
"time"
"google.golang.org/grpc"
)
func main() {
interceptor := 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":"10101",
"appkey":"i am key",
})
ctx = metadata.NewOutgoingContext(context.Background(), md)
err := invoker(ctx, method, req, reply, cc, opts...)
fmt.Printf("耗时:%s\n", time.Since(start))
return err
}
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
conn, err := grpc.Dial("127.0.0.1:50051", opts...)
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
grpc.WithPerRPCCredentials
client.go
package main
import (
"context"
"fmt"
"grpctest/grpc_token_auth_test/proto"
"google.golang.org/grpc"
)
type customCredential struct{}
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appid": "101010",
"appkey": "i am key",
}, nil
}
// RequireTransportSecurity indicates whether the credentials requires
// transport security.
func (c customCredential) RequireTransportSecurity() bool {
return false
}
func main() {
grpc.WithPerRPCCredentials(customCredential{})
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
opts = append(opts, grpc.WithPerRPCCredentials(customCredential{}))
conn, err := grpc.Dial("127.0.0.1:50051", opts...)
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
验证器
install
linux
第1种
git clone https://github.com/envoyproxy/protoc-gen-validate.git
cd protoc-gen-validate
go build
sudo cp protoc-gen-validate /bin
第2种
go get -d github.com/envoyproxy/protoc-gen-validate
cd $GOMODCACHE/github.com/envoyproxy/protoc-gen-validate@v0.6.1
make build //安装在 $GOPATH/bin
cp $GOPATH/bin/protoc-gen-validate /bin
生成pb.go文件
- 将validate.proto导入项目
cp $GOMODCACHE/github.com/envoyproxy/protoc-gen-validate@v0.6.1/validate/validate.proto ./proto
- 在项目中的proto文件中import validate.proto
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[3456789]\\d{9}$"}];
}
- 生成源码
protoc --go_out=plugins=grpc:. --validate_out="lang=go:." helloworld.proto
另一种生成方法,不需要将validate.proto导入到项目
protoc \
-I . \
-I ${GOPATH}/src \
-I ${GOPATH}/src/github.com/envoyproxy/protoc-gen-validate \
--go_out=":../generated" \
--validate_out="lang=go:../generated" \
example.proto
server.go
package main
import (
"context"
"net"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/grpc"
"grpcStream/grpc_validate/proto"
)
type Server struct{}
func (s *Server) SayHello(ctx context.Context, request *proto.Person) (*proto.Person,
error) {
return &proto.Person{
Id: 32,
}, nil
}
//req转换为Validator
type Validator interface {
Validate() error
}
func main() {
/*
p := new(Person)
err := p.Validate()
*/
var interceptor grpc.UnaryServerInterceptor
interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
// 继续处理请求,验证参数是否合法
//(proto.Person) 可以满足person的验证, 所有的接口 还有其他的接口那怎么办
if r, ok := req.(Validator); ok {
if err := r.Validate(); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
}
return handler(ctx, req)
}
var opts []grpc.ServerOption
opts = append(opts, grpc.UnaryInterceptor(interceptor))
g := grpc.NewServer(opts...)
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}
client.go
package main
import (
"context"
"fmt"
"grpcStream/grpc_validate/proto"
"google.golang.org/grpc"
)
// type customCredential struct{}
func main() {
var opts []grpc.DialOption
//opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
opts = append(opts, grpc.WithInsecure())
conn, err := grpc.Dial("localhost:50051", opts...)
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
//rsp, _ := c.Search(context.Background(), &empty.Empty{})
rsp, err := c.SayHello(context.Background(), &proto.Person{
Id: 1000,
Email: "bobby@imooc.com",
Mobile: "18888888888",
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Id)
}
异常处理
grpc的状态码
https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
syntax = "proto3";
option go_package = ".;proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
服务端
st := status.New(codes.InvalidArgument,"invalid username")
package main
import (
"context"
"net"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/grpc"
"grpcStream/grpc_error/proto"
)
type Server struct{}
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
error) {
//底层使用New
// return nil,status.New(codes.NotFound,"invalid username") 返回值*Status
// 使用status.Error 或 status.Errorf
return nil, status.Errorf(codes.NotFound, "记录未找到:%s", request.Name)
}
func main() {
g := grpc.NewServer()
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}
客户端
st,ok := status.FromError(err)
if !ok {
//Error was not a status error
}
st.Message()
st.Code()
package main
import (
"context"
"fmt"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
"grpcStream/grpc_error/proto"
)
func main() {
//stream
conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
//go语言推荐的是返回一个error和一个正常的信息
ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
_, err = c.SayHello(ctx, &proto.HelloRequest{Name: "bobby"})
if err != nil {
st, ok := status.FromError(err)
if !ok {
// Error was not a status error
panic("解析error失败")
}
fmt.Println(st.Message())
fmt.Println(st.Code())
}
}
超时机制
ctx,cancel := context.WithTimeout(context.TODO(),time.Second*3)
defer cancel()
r,err := c.SayHello(ctx,&pb.HelloRequest{Name: name})