rpc入门笔记 0x02 protobuf的杂七杂八

syntax = "proto3"; // 这是个proto3的文件

message HelloRequest{  // 创建数据对象
  string name = 1; // name表示名称,编号是1
}

生成python文件

安装grpcio和grpcio-tools库

pip install grpcio #安装grpc
pip install grpcio-tools #安装grpc tools

生成proto的python文件

python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. helloworld.proto
  • python -m grpc_tools.protoc:使用grpc_tools包中的protoc命令进行代码生成。
  • --python_out=.:指定生成的Python代码的存放位置为当前目录。
  • --grpc_python_out=.:指定生成的gRPC代码的存放位置为当前目录。
  • -I.:指定搜索.proto文件的路径为当前目录

总结起来,该命令的作用是将当前目录下的helloworld.proto文件生成对应的Python代码,并将生成的代码存放在当前目录中

python使用grpc

syntax = "proto3";

// The greeting service definition.
service Greeter {
  // Sends a greeting 下面就是暴露出来的一些方法
  rpc SayHello (HelloRequest) returns (HelloReply) {} // 定义返回什么类型就要返回什么类型

  rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}

  rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

生成出来的文件就直接使用他

go使用grpc

生成指令:

#好像是版本不兼容了 protoc -I . helloworld.proto --go_out=plugins=grpc:.
protoc -I . --go_out=. --go-grpc_out=. ./hello.proto

样例:

syntax = "proto3";
option go_package = ".;proto"; // 这个是必须加的

// The greeting service definition.
service Greeter {
  // Sends a greeting 下面就是暴露出来的一些方法
  rpc SayHello (HelloRequest) returns (HelloReply) {} // 定义返回什么类型就要返回什么类型
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

https://blog.csdn.net/neve_give_up_dan/article/details/126920398

https://github.com/grpc/grpc-go/issues/3794

https://blog.csdn.net/Mirale/article/details/122736894

UnimplementedXxxServer的作用:https://blog.csdn.net/Canon_in_D_Major/article/details/108135724

https://zhuanlan.zhihu.com/p/660634947

go_package

option go_package = "common/stream/proto/v1";

表示生成的Go代码文件应该位于common/stream/proto/v1这个包路径下。换句话说,生成的Go代码文件将被放置在common/stream/proto/v1目录下,并且其package声明会是v1

流模式

grpc的流模式主要有三种:

  • 客户端流模式
  • 服务端流模式
  • 双向流模式

proto文件:stream_proto.proto

syntax = "proto3";

option go_package = ".;proto";

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 (
	"GoRpc_quick/stream_grpc_test/proto"
	"fmt"
	"google.golang.org/grpc"
	"net"
	"sync"
	"time"
)

const PORT = ":8080"

type server struct {
	proto.UnimplementedGreeterServer
}

// 服务端流模式
func (s *server) GetStream(req *proto.StreamReqData, streamServer proto.Greeter_GetStreamServer) error {
	i := 0
	for true {
		streamServer.Send(&proto.StreamResData{
			Data: fmt.Sprintf("%v\n + %v", time.Now().Unix(), req.Data),
		})
		time.Sleep(time.Second)
		if i++; i > 10 {
			break
		}
	}
	return nil
}

// 客户端流模式
func (s *server) PostStream(streamServer proto.Greeter_PostStreamServer) error {
	for {
		recv, err := streamServer.Recv()
		if err != nil {
			fmt.Println(err)
			break
		}

		fmt.Println(recv.Data)
	}
	return nil
}

// 双向流模式
func (s *server) AllStream(streamServer proto.Greeter_AllStreamServer) error {
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() { // 负责receive
		defer wg.Done()
		for {
			recv, _ := streamServer.Recv()
			fmt.Println("收到客户端消息:", recv.Data)
		}
	}()

	go func() {
		defer wg.Done()
		for i := 0; i < 10; i++ {
			streamServer.Send(&proto.StreamResData{Data: "我是服务器"})
			time.Sleep(time.Second)
		}
	}()

	wg.Wait()

	return nil
}

func main() {
	listener, err := net.Listen("tcp", PORT)
	if err != nil {
		panic(err)
	}

	s := grpc.NewServer()
	proto.RegisterGreeterServer(s, &server{})
	err = s.Serve(listener)
	if err != nil {
		panic("failed to start grpc")
	}
}

client.go

package main

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

func main() {
	conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	// 服务端流模式(客户端接收流)
	c := proto.NewGreeterClient(conn)
	stream, _ := c.GetStream(context.Background(), &proto.StreamReqData{Data: "alice"})
	for {
		recv, err := stream.Recv() // 实际上就是socket编程
		if err != nil {
			fmt.Println(err.Error())
			break
		}
		fmt.Println(recv)
	}

	// 客户端流模式,客户端发送流
	postStream, err := c.PostStream(context.Background())
	for i := 0; i < 10; i++ {
		_ = postStream.Send(&proto.StreamReqData{Data: fmt.Sprintf("客户端流模式 + %d", i)})
		time.Sleep(time.Second)
	}

	// 双向流模式
	allStream, _ := c.AllStream(context.Background())
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() { // 负责receive
		defer wg.Done()
		for {
			recv, _ := allStream.Recv()
			fmt.Println("收到服务器消息:", recv.Data)
		}
	}()

	go func() {
		defer wg.Done()
		for i := 0; i < 10; i++ {
			allStream.Send(&proto.StreamReqData{Data: "我是客户端"})
			time.Sleep(time.Second)
		}
	}()

	wg.Wait()

}

protobuf知识汇总

基本类型会有对应

一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:

.proto TypeNotesPython TypeGo Type
doublefloatfloat64
floatfloatfloat32
int32使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代intint32
uint32使用变长编码intuint32
uint64使用变长编码intuint64
sint32使用变长编码,这些编码在负值时比int32高效的多intint32
sint64使用变长编码,有符号的整型值。编码时比通常的int64高效。intint64
fixed32总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。intuint32
fixed64总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。intuint64
sfixed32总是4个字节intint32
sfixed64总是8个字节intint64
boolboolbool
string一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。strstring
bytes可能包含任意顺序的字节数据。str[]byte

protobuf是会有一个默认值,即使不传,也会有默认值

当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:

  • 对于strings,默认是一个空string
  • 对于bytes,默认是一个空的bytes
  • 对于bools,默认是false
  • 对于数值类型,默认是0
  • 对于枚举,默认是第一个定义的枚举值,必须为0;
  • 对于消息类型(message),域没有被设置,确切的消息是根据语言确定的,详见generated code guide

使用repeated创建数组并使用python传递

client.py

# 方式1
with grpc.insecure_channel("localhost:8080") as channel:
    stub = helloword_pb2_grpc.GreeterStub(channel)
    hello_request = helloword_pb2.HelloRequest(id=[1, 2, 3,])  # 先实例化不赋值
    hello_request.name = "bob"
    res: helloword_pb2.HelloReply = stub.SayHello(hello_request)
        print(res.message)

# 方式2
with grpc.insecure_channel("localhost:8080") as channel:
    stub = helloword_pb2_grpc.GreeterStub(channel)
    hello_request = helloword_pb2.HelloRequest()  # 先实例化不赋值
    hello_request.name = "bob"
    hello_request.id.extend([1, 2])  # 这个对象已经有默认值创建好了,我们把他当做一个list来进行操作
    hello_request.id.append(4)
    res: helloword_pb2.HelloReply = stub.SayHello(hello_request)
        print(res.message)

嵌套message定义的使用

// The response message containing the greetings
message HelloReply {
  string message = 1;

  message Result { // 在里面定义message
    string name = 1;
    string url = 2;
  }

  repeated Result data = 2; // 定义一个message数组
}

py中的嵌套调用:

from grpc_hello.proto.helloword_pb2 import HelloReply  #  使用嵌套里面的数据

res = HelloReply.Result()
# 从哪嵌套的从哪调用

go中的嵌套调用:

import "GoRpc_quick/grpc_test/proto"

proto.Pong{} // 直接调用

map与enum枚举类型等其他类型

// 使用枚举类型,使得类型只能是下面的某一个
enum Gender{
  MALE = 0;
  FEMALE = 1;
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
  string url = 2;
  Gender g = 3;
  map<string, string> mp = 4;
  google.protobuf.Timestamp addTime = 5; // 时间戳
}

python grpc配合asynio使用

现在已经有官方的api了https://grpc.github.io/grpc/python/grpc_asyncio.html

这里使用grpclib库来实现:

安装依赖包

pip install grpclib

# 生成对应文件
python -m grpc_tools.protoc -I. --python_out=. --grpclib_python_out=. helloworld.proto

go-metadata

go中使用metadata

metadata使得client和server能够为对方提供关于本次调用的一些信息,就像一次http请求的RequestHeader和ResponseHeader一样。http中header的生命周周期是一次http请求,那么metadata的生命周期就是一次RPC调用

1. 新建metadata

MD 类型实际上是map,key是string,value是string类型的slice。

type MD map[string][]string

创建的时候可以像创建普通的map类型一样使用new关键字进行创建:

//第一种方式
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", // 使用逗号
)
2. 发送metadata
md := metadata.Pairs("key", "val")

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

// 单向 RPC
response, err := client.SomeRPC(ctx, someRequest)
3. 接收metadata
func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, err) {
    md, ok := metadata.FromIncomingContext(ctx)
    // do something with metadata
}

2. grpc中使用metadata

proro

syntax = "proto3";
option go_package = ".;proto";

// The greeting service definition.
service Greeter {
  // Sends a greeting 下面就是暴露出来的一些方法
  rpc SayHello (HelloRequest) returns (HelloReply) {} // 定义返回什么类型就要返回什么类型
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

server.go

type Server struct {
	*proto.UnimplementedGreeterServer
}

// 参数设置是硬性规定的
func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		fmt.Println("get metadata error")
	}
    // 遍历一下这个string的map
	for k, v := range md {
		fmt.Println(k, v)
	}
	if nameSlice, ok := md["name"]; ok { // 判断map中是否含有元素
		fmt.Println(nameSlice)
		for i, e := range nameSlice {
			fmt.Println(i, e)
		}
	}
	return &proto.HelloReply{
		Message: "Go hello " + req.Name,
	}, nil
}

func main() {
	// 一样的新建server
	g := grpc.NewServer()
	// 注册服务
	proto.RegisterGreeterServer(g, &Server{})

	listen, err := net.Listen("tcp", "0.0.0.0:8080")
	if err != nil {
		panic("failed to listen: " + err.Error())
	}
	err = g.Serve(listen)
	if err != nil {
		panic("failed to start grpc: " + err.Error())
	}
}

client.go

func main() {
	conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}

	defer conn.Close()

	c := proto_bak.NewGreeterClient(conn)

	//md := metadata.Pairs("timestamp", time.Now().Format(times))
	md := metadata.New(map[string]string{
		"name": "alice",
		"pwd":  "12345",
	})
	ctx := metadata.NewOutgoingContext(context.Background(), md)
	r, err := c.SayHello(ctx, &proto_bak.HelloRequest{
		Name: "bob",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(r.Message)

}

python-meta

server.py

import grpc

from grpc_metadata_test.proto import helloword_pb2, helloword_pb2_grpc

if __name__ == '__main__':
    with grpc.insecure_channel("localhost:50051") as channel:
        stub = helloword_pb2_grpc.GreeterStub(channel)
        hello_request = helloword_pb2.HelloRequest()  # 先实例化不赋值
        hello_request.name = "bob"

        rsp, call = stub.SayHello.with_call(
            hello_request,
            metadata=(
                ('name', 'bobby'),
                ('pwd', '123456')
            )
        )
        # call 能拿到服务器发回来的东西

        print(rsp.message)

client.py

import grpc
from concurrent import futures
from grpc_metadata_test.proto import helloword_pb2, helloword_pb2_grpc


class Greeter(helloword_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        # 一样的遍历metadata的map
        for k, v in context.invocation_metadata():
            print(k, v)
        return helloword_pb2.HelloReply(message=f'Python 你好{request.name}')


if __name__ == '__main__':
    # 1. 实例化server
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))  # 设置十个线程

    # 2. 注册逻辑到server中
    helloword_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)

    # 3. 启动server
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()

拦截器

go拦截器

server.go

type Server struct {
	*proto.UnimplementedGreeterServer
}

// 参数设置是硬性规定的
func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
	return &proto.HelloReply{
		Message: "Go hello " + req.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) // handler是原本的调用逻辑
		fmt.Println("请求已经完成")
		return res, err
	}

	opt := grpc.UnaryInterceptor(interceptor) // 参数是一个函数,所以参数必须一致(go认参数来区分函数
	// 一样的新建server
	g := grpc.NewServer(opt) // 放进去
	// 注册服务
	proto.RegisterGreeterServer(g, &Server{})

	listen, err := net.Listen("tcp", "0.0.0.0:8080")
	if err != nil {
		panic("failed to listen: " + err.Error())
	}
	err = g.Serve(listen)
	if err != nil {
		panic("failed to start grpc: " + err.Error())
	}
}

client.go

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
	} // 拦截器逻辑
	opt := grpc.WithUnaryInterceptor(interceptor) // 需要在dial(拨号)的时候传入参数
	conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure(), opt)

	if err != nil {
		panic(err)
	}

	defer conn.Close()

	c := proto.NewGreeterClient(conn)

	r, err := c.SayHello(context.Background(), &proto.HelloRequest{
		Name: "bob",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(r.Message)

}

python拦截器

server.py

class Greeter(helloword_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        for k, v in context.invocation_metadata():
            print(k, v)
        return helloword_pb2.HelloReply(message=f'Python 你好{request.name}')

# 调用拦截器逻辑需要继承该抽象类,并且实现这个抽象类的方法
class LogInterceptors(grpc.ServerInterceptor):
    def intercept_service(self, continuation, handler_call_details):
        print("请求开始")
        print(type(handler_call_details))
        rsp = continuation(handler_call_details)  # 相当于handler
        print("请求结束")
        return rsp


if __name__ == '__main__':
    # 实例化一个inter
    interceptor = LogInterceptors()
    # 1. 实例化server
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), interceptors=(interceptor,))  # 设置十个线程
   	# 并传进去interceptor的tuple或者list, 最后一定要带个,

    # 2. 注册逻辑到server中
    helloword_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)

    # 3. 启动server
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()

client.py

客户端的拦截器

# 第二个参数需要继承重写
class DefaultInterceptor(grpc.UnaryUnaryClientInterceptor):
    def intercept_unary_unary(self, continuation, client_call_details, request):
        # 拦截器业务逻辑
        start = datetime.now()
        rsp = continuation(client_call_details, request)
        print((datetime.now() - start).microseconds / 1000, "ms")
        return rsp


if __name__ == '__main__':
    # 实例化对象
    default_intercept = DefaultInterceptor()
    with grpc.insecure_channel("localhost:50051") as channel:
        # 需要对channel进行操作
        intercept_channel = grpc.intercept_channel(channel, default_intercept)

        stub = helloword_pb2_grpc.GreeterStub(intercept_channel)
        hello_request = helloword_pb2.HelloRequest()  # 先实例化不赋值
        hello_request.name = "bob"
        rsp, call = stub.SayHello.with_call(
            hello_request,
            metadata=(
                ('name', 'bobby'),
                ('pwd', '123456')
            )
        )
        # call 能拿到服务器发回来的东西

        print(rsp.message)

验证器

docs:protoc-gen-validate/docs.md at main · bufbuild/protoc-gen-validate · GitHub

每个字段要满足一个验证规则

编译命令:

protoc \
  -I . \
  -I path/to/validate/ \
  --go_out=":../generated" \
  --validate_out="lang=go:../generated" \
  example.proto
  
protoc -I . --go_out=. --go-grpc_out=. --validate_out="lang=go:." ./hello.proto

需要去拷贝一份validate.proto放到文件中:protoc-gen-validate/validate/validate.proto at main · bufbuild/protoc-gen-validate · GitHub

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:   "^(?:(?:\\+|00)86)?1[3-9]\\d{9}$",max_bytes: 256,}];

}

server.go

package main

import (
   "context"
   "google.golang.org/grpc/codes"
   "google.golang.org/grpc/status"
   "net"

   "google.golang.org/grpc"

   "GoRpc_quick/grpc_validate_test/proto"
)

type Server struct {
   *proto.UnimplementedGreeterServer
}

func (s *Server) SayHello(ctx context.Context, request *proto.Person) (*proto.Person,
   error) {
   return &proto.Person{
      Id: 32,
   }, nil
}

type Validator interface {
   Validate() error
}

func main() {
   //p := new(proto.Person)
   //p.Id = 1000
   //err := p.Validate() // 会返回一个error来判断合法性
   //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 r, ok := req.(Validator); ok { // 实例化为Validator,用多态的方法调用Validate方法
         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 (
   "GoRpc_quick/grpc_validate_test/proto"
   "context"
   "fmt"
   "google.golang.org/grpc"
)

// 本质上封装了metadata的封装
type customCredential struct {
}

func main() {
   var opts []grpc.DialOption
   opts = append(opts, grpc.WithInsecure())

   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.Person{
      Id:     1000,
      Email:  "1234abc@qq.com",
      Mobile: "18888888888",
   })
   if err != nil {
      panic(err)
   }
   fmt.Println(r.Id)

}

错误处理与超时设定

python

客户端处理:

import grpc
from grpc_error_test.proto import helloword_pb2, helloword_pb2_grpc

if __name__ == '__main__':
    with grpc.insecure_channel("localhost:8080") as channel:
        stub = helloword_pb2_grpc.GreeterStub(channel)
        hello_request = helloword_pb2.HelloRequest()  # 先实例化不赋值
        hello_request.name = "bob"
        try:
            res: helloword_pb2.HelloReply = stub.SayHello(hello_request, timeout=3)
        except grpc.RpcError as e:
            d = e.details()
            print(d)
            status_code = e.code() # 获取code
            print(status_code.name)
            print(status_code.value)
        print(res.message)

服务端处理

import grpc
from concurrent import futures
from grpc_error_test.proto import helloword_pb2, helloword_pb2_grpc


class Greeter(helloword_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        context.set_code(grpc.StatusCode.NOT_FOUND)
        context.set_details("记录不存在")
        return helloword_pb2.HelloReply(message=f'Python 你好{request.name}')


if __name__ == '__main__':
    # 1. 实例化server
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))  # 设置十个线程

    # 2. 注册逻辑到server中
    helloword_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)

    # 3. 启动server
    server.add_insecure_port('[::]:8080')
    server.start()
    server.wait_for_termination()

Go

客户端处理

func main() {
   conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
   if err != nil {
      panic(err)
   }

   defer conn.Close()

   c := proto.NewGreeterClient(conn)

   // 设置3超时
   ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
   defer cancel()

   _, err = c.SayHello(ctx, &proto.HelloRequest{
      Name: "bob",
   })
   if err != nil { // 捕捉并解析错误
      st, ok := status.FromError(err)
      if !ok {
         panic("error解析失败")
      }
      fmt.Println(st.Message())
      fmt.Println(st.Code())
   }
   //fmt.Println(r.Message)

}

服务端处理:

package main

import (
   "GoRpc_quick/grpc_error_test/proto"
   "context"
   "google.golang.org/grpc"
   "google.golang.org/grpc/codes"
   "google.golang.org/grpc/status"
   "net"
)

type Server struct {
   *proto.UnimplementedGreeterServer
}

// 参数设置是硬性规定的
func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
   return &proto.HelloReply{
      Message: "Go hello " + req.Name,
   }, status.Error(codes.InvalidArgument, "未实现") // 返回错误码和描述
}

func main() {
   // 一样的新建server
   g := grpc.NewServer()
   // 注册服务
   proto.RegisterGreeterServer(g, &Server{})

   listen, err := net.Listen("tcp", "0.0.0.0:8080")
   if err != nil {
      panic("failed to listen: " + err.Error())
   }
   err = g.Serve(listen)
   if err != nil {
      panic("failed to start grpc: " + err.Error())
   }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值