• 【Golang | gRPC】使用gRPC实现简单远程调用


    环境:
    Golang: go1.18.2 windows/amd64
    grpc: v1.47.0
    protobuf: v1.28.0

    完整代码:
    https://github.com/WanshanTian/GolangLearning
    cd GolangLearning/RPC/gRPC

    1. 简介

    gRPC是一个基于C/S架构,使用protobuf作为传输协议进行远程过程调用的高性能框架,前文【Golang | gRPC】protocol buffer compiler\protoc的安装【Golang | gRPC】使用protoc编译.proto文件分别就protoc编译工具的安装和使用进行了详细的说明,下面通过一个demo具体说明gRPC的简单使用

    2. 实践

    现有下面一种场景:服务端保存着用户的年龄信息,客户端输入姓名,经RPC后获得对应的年龄

    2.1 proto文件

    2.1.1 新建gRPC文件夹,使用go mod init初始化,创建pb文件夹,新建query.proto文件

    syntax = "proto3";
    package pb;
    option go_package= ".;pb";
    
    // 定义查询服务包含的方法
    service Query {
      rpc GetAge (userInfo) returns (ageInfo) {}
    }
    
    // 请求用的结构体,包含一个name字段
    message userInfo {
      string name = 1;
    }
    
    // 响应用的结构体,包含一个age字段
    message ageInfo {
      int32 age = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    服务端实现一个查询(Query)服务,包含一个方法GetAge;从Golang层面理解,其实就是一个Query接口,实现了一个GetAge方法

    2.1.2 在.\gRPC\pb目录下使用protoc工具进行编译,在pb文件夹下直接生成.pb.go_grpc.pb.go文件

    protoc --go_out=./ --go-grpc_out=./ *.proto
    
    • 1

    在这里插入图片描述

    2.2 pb.go和grpc.pb.go文件

    2.2.1 查看query_grpc.pb.go中生成的关于QueryServer的定义

    type QueryServer interface {
    	GetAge(context.Context, *UserInfo) (*AgeInfo, error)
    	mustEmbedUnimplementedQueryServer()
    }
    
    // UnimplementedQueryServer must be embedded to have forward compatible implementations.
    type UnimplementedQueryServer struct {
    }
    
    func (UnimplementedQueryServer) GetAge(context.Context, *UserInfo) (*AgeInfo, error) {
    	return nil, status.Errorf(codes.Unimplemented, "method GetAge not implemented")
    }
    func (UnimplementedQueryServer) mustEmbedUnimplementedQueryServer() {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    QueryServer就是根据proto文件中定义的service Query{}生成的接口,GetAge是我们定义的方法,mustEmbedUnimplementedQueryServer()这个方法是protoc自行编译出的(https://github.com/grpc/grpc-go/issues/3669这里有详细的讨论,在通过protoc编译时,也可以加参数取消这个方法,protoc --go_out=./ --go-grpc_out=require_unimplemented_servers=false:./ *.proto)

    2.2.2 查看pb.go中关于UserInfoAgeInfo的定义

    type UserInfo struct {
    	state         protoimpl.MessageState
    	sizeCache     protoimpl.SizeCache
    	unknownFields protoimpl.UnknownFields
    
    	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    }
    func (x *UserInfo) GetName() string {
    	if x != nil {
    		return x.Name
    	}
    	return ""
    }
    type AgeInfo struct {
    	state         protoimpl.MessageState
    	sizeCache     protoimpl.SizeCache
    	unknownFields protoimpl.UnknownFields
    
    	Age int32 `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"`
    }
    func (x *AgeInfo) GetAge() int32 {
    	if x != nil {
    		return x.Age
    	}
    	return 0
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    UserInfoAgeInfo结构体的前三个字段暂且用不到,通过方法Get...可以得到自定义字段的值

    2.3 服务端

    在gRPC目录下新建Server文件夹,新建main.go文件
    2.3.1 下面我们通过Query这个结构体具体实现QueryServer接口

    var userinfo = map[string]int32{
    	"foo": 18,
    	"bar": 20,
    }
    // Query 实现了QueryServer接口
    type Query struct {
    	pb.UnimplementedQueryServer // 通过结构体嵌套的方式默认实现mustEmbedUnimplementedQueryServer()这个方法,查看2.2.1
    }
    
    func (q *Query) GetAge(ctx context.Context, info *pb.UserInfo) (*pb.AgeInfo, error) {
    	age := userinfo[info.GetName()]
    	var res = new(pb.AgeInfo)
    	res.Age = age
    	return res, nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.3.2 服务注册并启动

    func main() {
    	// 创建socket监听器
    	listener, err := net.Listen("tcp", ":1234")
    	if err != nil {
    		log.Panic(err)
    	}
    	// new一个gRPC服务器,用来注册服务
    	grpcserver := grpc.NewServer()
    	// 注册服务方法
    	pb.RegisterQueryServer(grpcserver, new(Query))
    	// 开启gRPC服务
    	err = grpcserver.Serve(listener)
    	if err != nil {
    		log.Panic(err)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    使用RegisterQueryServer这个方法向gRPC服务器里注册服务。这里有两个服务,很容易概念搞混,一个是gRPC服务(用来监听,完成一些网络、路由等工作),一个是业务层面的服务(这些服务可以理解为包含一定方法的接口,用来给客户端进行调用)

    2.4 客户端

    在gRPC目录下新建Client文件夹,新建main.go文件
    2.4.1 先建立无认证的连接,生成Client,然后进行方法调用

    package main
    
    import (
    	"context"
    	"fmt"
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/credentials/insecure"
    	"log"
    	"wanshantian/grpc/pb"
    )
    
    func main() {
    	//建立无认证的连接
    	conn, err := grpc.Dial(":1234", grpc.WithTransportCredentials(insecure.NewCredentials()))
    	if err != nil {
    		log.Panic(err)
    	}
    	defer conn.Close()
    	client := pb.NewQueryClient(conn)
    	//RPC方法调用
    	age, _ := client.GetAge(context.Background(), &pb.UserInfo{Name: "foo"})
    	fmt.Println(age)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    注:

    • 使用grpc.WithTransportCredentials(insecure.NewCredentials())建立无认证的连接
    • 使用client := pb.NewQueryClient(conn)生成gRPC客户端

    运行结果如下:

    age:18
    
    • 1

    3 总结

    • 先创建proto文件,定义messageservice
    • 根据编译生成的pb.gogrpc.pb.go文件具体实现接口
    • 服务端注册服务并启动,客户端建立连接进行方法调用
  • 相关阅读:
    《JAVA EE》内部类(下篇)&Lambda表达式
    太绝了!NB!阿里第九版Java系统架构师+应用架构师面试突击宝典大全
    Leetcode 238. 除自身以外数组的乘积
    深入了解Kubernetes(k8s):安装、使用和Java部署指南(持续更新中)
    基于小程序的教学辅助微信小程序设计+ssm(lw+演示+源码+运行)
    git概念及命令
    全国爱眼教育大会,2022第四届北京国际青少年眼健康产业展会
    中小企业有哪些平台可以引流?SEO搜索流量与推荐信息流流量怎么选?
    Kyligence 当选 Gartner 2022 中国数据管理 Cool Vendor
    【周周Python百日刷题计划】Day5~内置函数和运算符的使用
  • 原文地址:https://blog.csdn.net/weixin_42216109/article/details/125470176