GRPC小小入门
GRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。GRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.
在 gRPC里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC系统类似, gRPC也是基于以下理念:
- 定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。
- 在服务端实现这个接口,并运行一个 gRPC服务器来处理客户端调用。
在客户端拥有一个存根能够像服务端一样的方法。 gRPC客户端和服务端可以在多种环境中运行和交互 -从 google内部的服务器到你自己的笔记本,并且可以用任何 gRPC支持的语言 来编写。
所以,你可以很容易地用 Java创建一个 gRPC服务端,用 Go、 Python、Ruby来创建客户端。此外, Google最新 API将有 gRPC版本的接口,使你很容易地将 Google的功能集成到你的应用里。
参考资料
gRPC 官方文档中文版:http://doc.oschina.net/grpc?t=60133
gRPC官网:https://grpc.io
再详细了解使用GRPC之前先来了解一下上面定义中的一些关键词。
首先我们来看一下HTTP/2是什么内容?
其实本质上就是http2.0版本,http目前为止主要有四个版本,分别为http1.0、http1.1、http2.0、https。
http1.0是最原始的版本,不支持持久连接,效率也比较低
http1.1针对http1.0版本做了优化,可以连接一次,多次使用,效率比http1.0高
http2.0实现了多路复用,对http1.1再次进行了优化。http2.0也被称为下一代http协议,是在2013年8月进行首次测试,所以现在用的不是很广。
https其实是在http协议上多加了一层SSL协议,具体如下图:
所以本质上,http1.0、http1.1、http2.0都可以添加SSL协议。
grpc环境安装
- 官方推荐安装方法:
go get -u -v google.golang.org/grpc
但是由于一些原因,大部分同学不能直接访问google官网,所以只能曲线救国了
- 通过github下载各种依赖库,然后配置。 ```go git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto
cd $GOPATH/src/ go install google.golang.org/grpc
网络畅通可以用上述方法,但如果网速较慢,我们也可以选择离线安装方法。3. 用x.zip和google.golang.org.zip两个离线包来安装。```go#将x.zip 解压到 $GOPATH/src/golang.org/x 目录下$ unzip x.zip -d $GOPATH/src/golang.org/x#将google.golang.org.zip 解压到 $GOPATH/src/google.golang.org 目录下$ unzip google.golang.org.zip -d $GOPATH/src/google.golang.org#然后进入到$GOPATH/src/google.golang.org/grpc下面执行go install$ go install
GRPC使用
如果从Protobuf的角度看,GRPC只不过是一个针对service接口生成代码的生成器。接着我们来学习一下GRPC的用法。这里我们创建一个简单的proto文件,定义一个HelloService接口:
syntax = "proto3"; //指定版本信息package pb; //后期生成go文件的包名message Person{// 名字string name = 1;// 年龄int32 age = 2 ;}//定义RPC服务service HelloService {rpc Hello (Person)returns (Person);}
对proto文件进行编译:$ protoc --go_out=plugins=grpc:. *.proto
GRPC插件会为服务端和客户端生成不同的接口:
//客户端接口type HelloServiceClient interface {Hello(ctx context.Context, in *Person, opts ...grpc.CallOption) (*Person, error)}//服务器接口type HelloServiceServer interface {Hello(context.Context, *Person) (*Person, error)}
我们接着可以基于他们给的服务端接口重新实现HelloService服务:
type HelloService struct{}func (this*HelloService)Hello(ctx context.Context, person *pb.Person) (*pb.Person, error){reply := &pb.Person{Name:"zhangsan" + person.Name,Age:18,}return reply,nil}
GRPC的启动流程和RPC的启动流程类似,代码如下:
func main(){//获取grpc服务端对象grpcServer := grpc.NewServer()//注册grpc服务pb.RegisterHelloServiceServer(grpcServer,new(HelloService))//设置服务端监听lis,err := net.Listen("tcp",":1234")if err != nil {panic(err)}//在指定端口上提供grpc服务grpcServer.Serve(lis)}
然后我们就可以通过客户端来连接GRPC服务了:
func main(){//和grpc服务建立连接conn,err := grpc.Dial("localhost:1234",grpc.WithInsecure())if err != nil {panic(err)}defer conn.Close()client := pb.NewHelloServiceClient(conn)reply,err := client.Hello(context.Background(),&pb.Person{Name:"lisi",Age:100})if err != nil {panic(err)}fmt.Println("reply,",reply)}
服务端
// 定义类type Children struct {}// 按接口绑定类方法func (this *Children) SayHello(ctx context.Context, t *pb.Teacher) (*pb.Teacher, error) {t.Name += " is Sleeping"return t, nil}func main() {//1. 初始一个 grpc 对象grpcServer := grpc.NewServer()//2. 注册服务pb.RegisterSayNameServer(grpcServer, new(Children))//3. 设置监听, 指定 IP、portlistener, err := net.Listen("tcp", "127.0.0.1:8800")if err != nil {fmt.Println("Listen err:", err)return}defer listener.Close()//4. 启动服务。---- serve()grpcServer.Serve(listener)}
客户端
package mainimport ("google.golang.org/grpc""fmt""day01/pb""context")func main() {//1. 连接 grpc 服务grpcConn, err := grpc.Dial("127.0.0.1:8800", grpc.WithInsecure())if err != nil {fmt.Println("grpc.Dial err:", err)return}defer grpcConn.Close()//2. 初始化 grpc 客户端grpcClient := pb.NewSayNameClient(grpcConn)// 创建并初始化Teacher 对象var teacher pb.Teacherteacher.Name = "itcast"teacher.Age = 18//3. 调用远程服务。t, err := grpcClient.SayHello(context.TODO(), &teacher)fmt.Println(t, err)}
protobuf
syntax = "proto3";package pb;// 消息体 --- 一个package 中,不允许定义同名的消息体message Teacher {int32 age = 1;string name = 2;}// 定义 服务service SayName {rpc SayHello (Teacher) returns (Teacher);}
type SayNameClient interface {SayHello(ctx context.Context, in *Teacher, opts ...grpc.CallOption) (*Teacher, error)}type sayNameClient struct {cc *grpc.ClientConn}func NewSayNameClient(cc *grpc.ClientConn) SayNameClient {return &sayNameClient{cc}}func (c *sayNameClient) SayHello(ctx context.Context, in *Teacher, opts ...grpc.CallOption) (*Teacher, error) {out := new(Teacher)err := c.cc.Invoke(ctx, "/pb.SayName/SayHello", in, out, opts...)if err != nil {return nil, err}return out, nil}
type SayNameServer interface {SayHello(context.Context, *Teacher) (*Teacher, error)}// UnimplementedSayNameServer can be embedded to have forward compatible implementations.type UnimplementedSayNameServer struct {}func (*UnimplementedSayNameServer) SayHello(ctx context.Context, req *Teacher) (*Teacher, error) {return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")}func RegisterSayNameServer(s *grpc.Server, srv SayNameServer) {s.RegisterService(&_SayName_serviceDesc, srv)}
电商项目学grpc和protobuf
有了rpc为什么还要学grpc?
从RPC封装哪里,可以知道,需要我们手动对客户端和服务端进行封装。grpc都是自动按照统一风格进行封装。
//1. 这些概念在grpc中都有对应
//2. 发自灵魂的拷问: server_proxy 和 client_proxy能否自动生成啊 为多种语言生成
//3. 都能满足 这个就是protobuf + grpc
什么是grpc和protobuf
grpc
gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.
protobuf
java中的dubbo dubbo/rmi/hessian messagepack 如果你懂了协议完全有能力自己去实现一个协议
- 习惯用
Json、XML数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer其实 是Google出品的一种轻量 & 高效的结构化数据存储格式,性能比Json、XML真的强!太!多!- protobuf经历了protobuf2和protobuf3,pb3比pb2简化了很多,目前主流的版本是pb3

上面说的压缩性能好:如下图,json和protobuf压缩同样数据,proto压缩后更短(也可以单独用,不是非要和grpc绑定用)
在proto文件中写的所有的结构体都可以在生成的对应proto的go文件中找到
syntax = "proto3";option go_package = ".;proto";service Greeter {rpc SayHello (HelloRequest) returns (HelloReply);}message HelloRequest {string name = 1;}message HelloReply {string message = 1;}
protoc -I . goods.proto --go_out=plugins=grpc:.
package mainimport ("context""fmt""google.golang.org/grpc""grpc_demo/hello""net")type Server struct {}//这里的context主要是对协程的超时控制或其他控制func (s *Server) SayHello(ctx context.Context,request *hello.HelloRequest)(*hello.HelloReply,error){return &hello.HelloReply{Message:"Hello "+request.Name},nil}func main() {//实例一个grpc的serverg := grpc.NewServer()s := Server{}hello.RegisterGreeterServer(g,&s)listen, err := net.Listen("tcp", fmt.Sprintf(":8080"))if err != nil {panic("failed to listen: "+err.Error())}g.Serve(listen)}
package mainimport ("context""fmt""google.golang.org/grpc""grpc_demo/proto")func main() {conn,err := grpc.Dial("127.0.0.1:8080",grpc.WithInsecure())if err!=nil{panic(err)}defer conn.Close()c := hello.NewGreeterClient(conn)r,err := c.SayHello(context.Background(),&hello.HelloRequest{Name:"bobby"})if err!=nil{panic(err)}fmt.Println(r.Message)}
proto文件生成go文件讲解
首先看message转换成什么如下图:前三个小写的不用管
server端自动帮我们生成了接口,我们需要做的就是实现接口中对应的业务逻辑



grpc的四种数据流
之前我们讲了 grpc 怎么简单的使用 ,这次讲讲 grpc 中的 stream,srteam 顾名思义就是一种流,可以源源不断的推送数据,很适合传输一些大数据,或者服务端和客户端长时间数据交互,比如客户端可以向 服务端订阅 一个数据,服务端就可以利用 stream ,源源不断地推送数据。
1. 简单模式(Simple RPC)
这种模式最为传统,即客户端发起一次请求,服务端响应一个数据,这和大家平时熟悉的RPC没有什么大的区别,所以不再详细介绍。
2. 服务端数据流模式(Server-side streaming RPC)
这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端
3. 客户端数据流模式(Client-side streaming RPC)
与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。典型的例子是物联网终端向服务器报送数据。
4.双向数据流模式(Bidirectional streaming RPC)
顾名思义,这是客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是可以实现实时交互。典型的例子是聊天机器人。
syntax = "proto3";//声明proto的版本 只能 是3,才支持 grpc//声明 包名option go_package=".;proto";//声明grpc服务service Greeter {/*以下分别是 服务端 推送流, 客户端 推送流 ,双向流。*///stream是关键字,代表流。可以写{}也可以写;。一个意思rpc GetStream (StreamReqData) returns (stream StreamResData){}//服务端流模式rpc PutStream (stream StreamReqData) returns (StreamResData){}//客户端流模式rpc AllStream (stream StreamReqData) returns (stream StreamResData){}//双向流}//stream请求结构message StreamReqData {string data = 1;}//stream返回结构message StreamResData {string data = 1;}


package mainimport ("fmt""google.golang.org/grpc""log""net""start/new_stream/proto""sync""time")const PORT = ":50052"type server struct {}//服务端 单向流,这里参数和一般的不一样,没有了context,具体要根据proto以后的函数func (s *server)GetStream(req *proto.StreamReqData, res proto.Greeter_GetStreamServer) error{i:= 0for{i++//res proto.Greeter_GetStreamServer下有一个send方法,代表服务端不断发送数据res.Send(&proto.StreamResData{Data:fmt.Sprintf("%v",time.Now().Unix())})time.Sleep(1*time.Second)if i >10 {break}}return nil}//客户端 单向流func (s *server) PutStream(cliStr proto.Greeter_PutStreamServer) error {for {//接收客户端推送过来的流数据,不断打印if tem, err := cliStr.Recv(); err == nil {log.Println(tem)} else {log.Println("break, err :", err)break}}return nil}//客户端服务端 双向流func(s *server) AllStream(allStr proto.Greeter_AllStreamServer) error {wg := sync.WaitGroup{}wg.Add(2)go func() {for {data, _ := allStr.Recv()log.Println(data)}wg.Done()}()go func() {for {allStr.Send(&proto.StreamResData{Data:"ssss"})time.Sleep(time.Second)}wg.Done()}()wg.Wait()return nil}func main(){//监听端口lis,err := net.Listen("tcp",PORT)if err != nil{panic(err)return}//创建一个grpc 服务器s := grpc.NewServer()//注册事件proto.RegisterGreeterServer(s,&server{})//处理链接err = s.Serve(lis)if err != nil {panic(err)}}
type GreeterClient interface {GetStream(ctx context.Context, in *StreamReqData, opts ...grpc.CallOption) (Greeter_GetStreamClient, error)PutStream(ctx context.Context, opts ...grpc.CallOption) (Greeter_PutStreamClient, error)AllStream(ctx context.Context, opts ...grpc.CallOption) (Greeter_AllStreamClient, error)}
package mainimport ("google.golang.org/grpc""context"_ "google.golang.org/grpc/balancer/grpclb""log""start/new_stream/proto""time")const (ADDRESS = "localhost:50052")func main(){//通过grpc 库 建立一个连接conn ,err := grpc.Dial(ADDRESS,grpc.WithInsecure())if err != nil{return}defer conn.Close()//通过刚刚的连接 生成一个client对象。c := proto.NewGreeterClient(conn)//调用服务端推送流reqstreamData := &proto.StreamReqData{Data:"aaa"}res,_ := c.GetStream(context.Background(),reqstreamData)//这句也是,之前没有的for {aa,err := res.Recv()//接收数据流if err != nil {log.Println(err)break}log.Println(aa)}//客户端 推送 流putRes, _ := c.PutStream(context.Background())i := 1for {i++putRes.Send(&proto.StreamReqData{Data:"ss"})time.Sleep(time.Second)if i > 10 {break}}//服务端 客户端 双向流allStr,_ := c.AllStream(context.Background())go func() {for {data,_ := allStr.Recv()log.Println(data)}}()go func() {for {allStr.Send(&proto.StreamReqData{Data:"ssss"})time.Sleep(time.Second)}}()select {}}
protobuf相关知识
proto文件同步时
当message里成员编码在调用微服务的时候,复制proto过去的时候,成员编码搞反了。返回的结果也是反的,因为protobuf是为了压缩空间根据编号进行处理的。
proto文件如果import另外一个proto里的message?
先建一个base的proto文件,把公共的message放进去如下图,Empty和Pong是调用别的文件的。
嵌套message
repeated是数组
嵌套 的时候,编码是独立的,不冲突
实例化使用Result时,名字是改变的,记得去proto.go中看看叫啥
枚举类型

转化后
使用枚举时,我们定义值的时候在枚举的里面选择一个值进行定义
map类型
时间戳timestamp
grpc小知识
go控制grpc的metadata



syntax = "proto3";option go_package = ".;proto";service Greeter {rpc SayHello (HelloRequest) returns (HelloReply);}//将 sessionid放入 放入cookie中 http协议message HelloRequest {string name = 1;}message HelloReply {string message = 1;}//go语言中是生成一个文件, 也就只有python会生成两个文件
package mainimport ("context""fmt""google.golang.org/grpc/metadata""net""google.golang.org/grpc""OldPackageTest/grpc_test/proto")type Server struct{}func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,error) {md, ok := metadata.FromIncomingContext(ctx)if ok {fmt.Println("get metadata error")}if nameSlice, ok := md["name"]; ok {fmt.Println(nameSlice)for i, e := range nameSlice {fmt.Println(i, e)}}return &proto.HelloReply{Message: "hello " + request.Name,}, nil}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())}}
package mainimport ("OldPackageTest/grpc_test/proto""context""fmt""google.golang.org/grpc""google.golang.org/grpc/metadata")func main() {//streamconn, err := grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())if err != nil {panic(err)}defer conn.Close()c := proto.NewGreeterClient(conn)//md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))md := metadata.New(map[string]string{"name": "bobby","pasword": "imooc",})ctx := metadata.NewOutgoingContext(context.Background(), md)r, err := c.SayHello(ctx, &proto.HelloRequest{Name: "bobby"})if err != nil {panic(err)}fmt.Println(r.Message)}
grpc拦截器
记录接口的访问时常、对接口验证用户登陆的情况。对所有的请求拦截一下,进行预处理。如果把这些逻辑在每一个接口都重新写一遍,就会有很多问题,比如忘记写,比如接口本身会被污染,后面进行维护时可能会出错。所以就需要一个统一的拦截器
syntax = "proto3";option go_package = ".;proto";package proto;service Greeter {rpc SayHello (HelloRequest) returns (HelloReply);}//将 sessionid放入 放入cookie中 http协议message HelloRequest {string name = 1;}message HelloReply {string message = 1;}//go语言中是生成一个文件, 也就只有python会生成两个文件
package mainimport ("context""fmt""net""time""google.golang.org/grpc""OldPackageTest/grpc_test/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)//拦截器在启动newserver时提前配置好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())}}
package mainimport ("context""fmt""time""google.golang.org/grpc/codes"grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry""google.golang.org/grpc""OldPackageTest/grpc_test/proto")func main() {//streaminterceptor := 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.DialOptionopts = 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)}
meradata和拦截器实现用户登录验证功能案例
为了安全性,不让所有人都能访问,实现一个用户登录的验证。
用户名和密码不适合携带在message中,因为很多接口都要的,每个message都要带的话,太罗嗦和麻烦。
拦截器可以不侵入业务逻辑,而把功能集成进来。使用metadata可以不侵入message将数据完整传送过来。
syntax = "proto3";option go_package = ".;proto";service Greeter {rpc SayHello (HelloRequest) returns (HelloReply);}//将 sessionid放入 放入cookie中 http协议//这个就好比文档,表单验证message HelloRequest {string name = 1;}message HelloReply {string message = 1;}//go语言中是生成一个文件, 也就只有python会生成两个文件
package mainimport ("context""fmt""google.golang.org/grpc/codes""google.golang.org/grpc/metadata""google.golang.org/grpc/status""net""google.golang.org/grpc""OldPackageTest/grpc_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 stringappkey 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())}}
package mainimport ("OldPackageTest/grpc_test/proto""context""fmt""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() {//stream//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//}grpc.WithPerRPCCredentials(customCredential{})var opts []grpc.DialOptionopts = 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)}
grpc异常处理
proto和前面都一样
package mainimport ("context""google.golang.org/grpc/codes""google.golang.org/grpc/status""net""google.golang.org/grpc""OldPackageTest/grpc_error_test/proto")type Server struct{}func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,error) {return nil, status.Errorf(codes.NotFound, "记录未找到:%s", request.Name)//return &proto.HelloReply{// Message: "hello "+request.Name,//}, nil}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())}}
package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/status""time""OldPackageTest/grpc_error_test/proto")func main() {//streamconn, 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 errorpanic("解析error失败")}fmt.Println(st.Message())fmt.Println(st.Code())}//fmt.Println(r.Message)}
grpc超时机制





左k右v




再返回去看metadata定义
