当前位置 博文首页 > 文章内容

    动手实现一个简单的 rpc 框架到入门 grpc (下)

    作者: 栏目:未分类 时间:2020-07-21 16:04:24

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    之前手动实现了一次简陋的 rpc 调用,为了简单使用了 json 编码信息,其实这是非常不可靠的,go 中 json 解析会有一些问题,比如整数会变成浮点数,而且 json 字符串比较占空间。

    gRPC 由 google 开发,是一款语言中立、平台中立、开源的 RPC 框架,默认使用 protocol buffers 来序列化和传输消息,基于 http2。

    protobuf

    Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

    先编写 proto 文件,再编译成 go 文件,新建 proto/hello.proto 文件

    // proto/hello.proto
    syntax = "proto3";
    
    package proto;
    
    message String {
        string value = 1;   // 类型 字段 字段标识符
    }
    
    // 定义服务
    service HelloService {
        rpc Hello (String) returns (String);
    }
    

    安装 protobuf 编译器用于编译 proto 文件,比如使用 scoop 安装 scoop install protobuf,或者手动下载安装

    protobuf 编译工具可以把 proto 模板编译成多种语言,默认不支持 go,安装一下 go 插件

    $ go get -u github.com/golang/protobuf/proto
    $ go get -u github.com/golang/protobuf/protoc-gen-go
    

    进入 hello.proto 文件目录,编译生成 hello.pb.go,后续为了方便可以把编译指令写成 shell 脚本

    $ protoc -I . --go_out=plugins=grpc:. ./hello.proto
    

    生成的 hello.pb.go 有一些接口和方法(省略了其他的),使用时候只需要实现接口调用对应方法

    type HelloServiceServer interface {
    	Hello(context.Context, *String) (*String, error)
    }
    
    func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {
    	s.RegisterService(&_HelloService_serviceDesc, srv)
    }
    
    type helloServiceClient struct {
    	cc grpc.ClientConnInterface
    }
    
    func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient {
    	return &helloServiceClient{cc}
    }
    
    func (c *helloServiceClient) Hello(ctx context.Context, in *String, opts ...grpc.CallOption) (*String, error) {
    	out := new(String)
    	err := c.cc.Invoke(ctx, "/proto.HelloService/Hello", in, out, opts...)
    	if err != nil {
    		return nil, err
    	}
    	return out, nil
    }
    

    grpc

    安装 grpc

    $ go get -u google.golang.org/grpc
    

    服务端

    在 proto 文件夹同级新建 sever/server.go 文件,实现 server 接口,注册服务

    type HelloServiceImpl struct{}
    
    func (p *HelloServiceImpl) Hello(ctx context.Context, args *proto.String) (*proto.String, error) {
    	reply := &proto.String{Value: "hello " + args.GetValue()}
    	return reply, nil
    }
    
    func main() {
    	grpcServer := grpc.NewServer()
    	proto.RegisterHelloServiceServer(grpcServer, new(HelloServiceImpl))
    
    	lis, err := net.Listen("tcp", ":1234")
    	if err != nil {
    		log.Fatal(err)
    	}
    	grpcServer.Serve(lis)
    }
    

    客户端

    在 proto 文件夹同级新建 sever/server.go 文件,调用服务

    func main() {
    	conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer conn.Close()
    
    	client := proto.NewHelloServiceClient(conn)
    	reply, err := client.Hello(context.Background(), &proto.String{Value: "world"})
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Println(reply.GetValue())
    }
    

    验证

    首先运行服务端 go run server/server.go,再运行客户端

    $ go run client/client.go
    hello world
    

    这里只简单介绍了用法,关于 protobuf 的更多用法可以参考 https://developers.google.com/protocol-buffers/,关于 protobuf 的原理可以参考 https://blog.csdn.net/carson_ho/article/details/70568606,另外 grpc 还支持流式调用。

    流式调用

    数据传输是流式的,服务端和服务端不用输完全部数据后才响应,流式传输可以是客户端流式、或服务端流式,也可以同时流式传输,只需要一个 stream 标识

    service HelloService {
        rpc Hello (String) returns (String);
        rpc HelloStream (String) returns (stream String);				//服务端流式响应
        //rpc HelloStream (stream String) returns (String);				//客户端端流式发送
        //rpc HelloStream (stream String) returns (stream String);		//双向流
    }
    

    重新编译,然后在 server/server.go 新增方法

    func (HelloServiceImpl) HelloStream(args *proto.String, stream proto.HelloService_HelloStreamServer) error {
    	for i := 0; i < 10; i++ {
    		_ = stream.Send(&proto.String{Value: "hello " + args.GetValue() + strconv.Itoa(i)})
    	}
    	return nil
    }
    

    client/client.go 中调用 HelloStream 方法

    stream, _ := client.HelloStream(context.Background(), &proto.String{Value: "World"})
    for {
    	res, err := stream.Recv()
    	if err == io.EOF {
    		break
    	}
    	if err != nil {
    		log.Printf("stream.Recv: %v", err)
    	}
    	log.Printf("%s", res.String())
    }
    

    验证,首先运行 server,然后运行 client

    $ go run server/server.go
    
    $ go run client/client.go
    2020/07/21 07:41:10 value:"hello World0"
    2020/07/21 07:41:10 value:"hello World1"
    2020/07/21 07:41:10 value:"hello World2"
    2020/07/21 07:41:10 value:"hello World3"
    2020/07/21 07:41:10 value:"hello World4"
    2020/07/21 07:41:10 value:"hello World5"
    2020/07/21 07:41:10 value:"hello World6"
    2020/07/21 07:41:10 value:"hello World7"
    2020/07/21 07:41:10 value:"hello World8"
    2020/07/21 07:41:10 value:"hello World9"