Grpc框架入门,无需视频,资料已上传gitee - Go语言中文社区

Grpc框架入门,无需视频,资料已上传gitee


GRPC框架


源代码:https://gitee.com/g_night/grpc-demo

安装

go get github.com/golang/protobuf/proto
go get google.golang.org/grpc
go get github.com/golang/protobuf/protoc-gen-go
go get google.golang.org/protobuf/reflect/protoreflect
go get google.golang.org/protobuf/runtime/protoimpl
  • 需要protoc-gen-go.exeprotoc.exe,在源代码的目录中有个protobuf工具,可以将这些可执行文件放到GOPATH/bin下

入门案例

  1. 编写中间文件:

创建 hello.proto

syntax = "proto3";
package service;

// 用于指定golang package的名字,新版本要求必须包含 /
option go_package = "service/";

message HelloRequest {
  int64 id = 1;
}

message HelloResponse {
  string name = 1;
}

service HelloService {
  rpc SayHello(HelloRequest) returns (HelloResponse);
}
  1. 核心服务代码:

运行指令,生成hello.pb.go

protoc --go_out=plugins=grpc:../ hello.proto
# 规则是: --go_out=目录 proto文件
# 注意:这里需要添加 plugins=grpc: 这个用于指定使用的服务

创建文件hello_service.go,编写核心服务:

package service

import (
	"context"
	"strconv"
)

// hello服务具体实现

type HelloService struct{}

// 方法名&参数  --> 就在 hello.pb.go 的 HelloServiceServer
func (server *HelloService) SayHello(ctx context.Context, req *HelloRequest) (*HelloResponse, error) {
	number := strconv.Itoa(int(req.Id))
	resp := &HelloResponse{Name: "hello word no." + number}
	return resp, nil
}
  1. 运行服务:

创建文件server.go

package main

import (
	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"
	"grpc-demo/service"
	"net"
)

// 具体server代码
func main() {

	// 创建新服务
	server := grpc.NewServer()
	// 注册服务
	service.RegisterHelloServiceServer(server, &service.HelloService{})

	// 设置端口监听服务
	listen, err := net.Listen("tcp", "127.0.0.1:9091")
	if err != nil {
		grpclog.Fatalf("Failed to listen: %v", err)
	}
	server.Serve(listen)
}
  1. 测试文件调用服务:

创建文件client_test.go

package main

// 客户端示例

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"
	"grpc-demo/service"
	"testing"
)

func TestClient(t *testing.T) {
	// 连接 127.0.0.1:9091,地址要一致
	conn, err := grpc.Dial("127.0.0.1:9091", grpc.WithInsecure())
	if err != nil {
		grpclog.Fatalln(err)
	}
	defer conn.Close()

	// 初始化客户端
	c := service.NewHelloServiceClient(conn)

	// 构造请求
	req := &service.HelloRequest{Id: 1}
	// 调用服务
	resp, err := c.SayHello(context.Background(), req)

	// 服务调用失败
	if err != nil {
		grpclog.Fatalln(err)
	}

	fmt.Println("resp.Name = ", resp.Name)
}

首先运行server.gomain方法,确保服务开启。然后运行client_test.go,就能看到显示:

=== RUN   TestClient
resp.Name =  hello word no.1 # 返回预期结果
--- PASS: TestClient (0.02s)

目录结构(源代码中位于demo1-helloword文件夹):

在这里插入图片描述

步骤总结:

  1. 编写中间文件:xxx.proto

  2. 生成代码:protoc --go_out=plugins=grpc:../ xxx.proto

  3. 编写服务核心代码,声明对象,实现方法接口(Name):

    func (s *XXX) Name(ctx context.Context, req *XXXRequest) (*XXXResponse, error)

  4. 运行服务:

    // 创建新服务
    server := grpc.NewServer()
    // 注册服务
    service.RegisterXXXServer(server, &service.XXXService{})
    
    // 设置端口监听服务
    listen, err := net.Listen("tcp", "127.0.0.1:9091")
    if err != nil {
    	grpclog.Fatalf("Failed to listen: %v", err)
    }
    server.Serve(listen)
    

进阶案例

Token鉴权

我们在demo1-helloword的基础上加入token校验,项目文件在源代码的demo2-token

  1. 引入认证包:

    go get google.golang.org/grpc/credentials
    
  2. 客户端加入token信息:

    编写customCredential自定义认证,实现PerRPCCredentials接口的两个方法:

    • GetRequestMetadata(ctx context.Context, uri …string) (map[string]string, error)

    • RequireTransportSecurity() bool

package main

// 客户端示例

import (
	"context"
	"demo2-token/service"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"
	"testing"
)

// customCredential 自定义认证
type customCredential struct{}

// GetRequestMetadata 实现自定义认证接口
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"token": "real_token",
	}, nil
}

// RequireTransportSecurity 自定义认证是否开启TLS
func (c customCredential) RequireTransportSecurity() bool {
	return false
}

func TestClient(t *testing.T) {
	var opts []grpc.DialOption
	opts = append(opts, grpc.WithInsecure())
	opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
	normalClient(opts)
}

// 常规请求操作
func normalClient(opts []grpc.DialOption) {
	// 连接 127.0.0.1:9091,地址要一致
	// 加入 token
	conn, err := grpc.Dial("127.0.0.1:9091", opts...)
	if err != nil {
		grpclog.Fatalln(err)
	}
	defer conn.Close()

	// 初始化客户端
	c := service.NewHelloServiceClient(conn)

	// 构造请求
	req := &service.HelloRequest{Id: 1}
	// 调用服务
	resp, err := c.SayHello(context.Background(), req)

	// 服务调用失败
	if err != nil {
		grpclog.Fatalln(err)
	}

	fmt.Println("resp.Name = ", resp.Name)
}
  1. 编写服务端,取出token进行校验:
package main

// 客户端示例

import (
	"context"
	"demo2-token/service"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"
	"testing"
)

// customCredential 自定义认证
type customCredential struct{}

// GetRequestMetadata 实现自定义认证接口
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"token": "real_token",
	}, nil
}

// RequireTransportSecurity 自定义认证是否开启TLS
func (c customCredential) RequireTransportSecurity() bool {
	return false
}

func TestClient(t *testing.T) {
	var opts []grpc.DialOption
	opts = append(opts, grpc.WithInsecure())
	opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
	normalClient(opts)
}

// 常规请求操作
func normalClient(opts []grpc.DialOption) {
	// 连接 127.0.0.1:9091,地址要一致
	// 加入token
	conn, err := grpc.Dial("127.0.0.1:9091", opts...)
	if err != nil {
		grpclog.Fatalln(err)
	}
	defer conn.Close()

	// 初始化客户端
	c := service.NewHelloServiceClient(conn)

	// 构造请求
	req := &service.HelloRequest{Id: 1}
	// 调用服务
	resp, err := c.SayHello(context.Background(), req)

	// 服务调用失败
	if err != nil {
		grpclog.Fatalln(err)
	}

	fmt.Println("resp.Name = ", resp.Name)
}

Protobuf语法总结

本文以 “proto3” 为依据!

  • 文件以.proto做为文件后缀,除结构定义外的语句以分号结尾

  • 结构定义可以包含:message、service、enum

  • 末尾的数字表示在序列化数组里面的顺序。可以定义为任何数字(不能重复),不需要总是从1或者0开始

  • Service、Rpc方法名、Message命名采用驼峰命名方式(首字母大写),字段命名采用 小写字母 + 下划线 分隔方式

  • Enum类型命名采用 驼峰命名 ,而字段命名采用 大写字母 + 下划线 分隔方式

    message HelloRequest {
      required string name = 1;
    }
    enum MyEnums {
      FIRST_VALUE = 1;
    }
    
  • Protobuf定义了一套基本数据类型,对应规则:

.protoC++JavaGo
doubledoubledoublefloat64
floatfloatfloatfloat32
int32int32intint32
int64int64longint64
uint32uint32intuint32
uint64uint64longuint64
sint32int32intint32
sint64int64longint64
fixed32uint32intuint32
fixed64uint64longuint64
sfixed32int32intint32
sfixed64int64longint64
boolboolbooleanbool
stringstringStringstring
bytesstringByteString[]byte

Message

message就相当于结构体,一般定义请求体、响应体。

字段修饰符(其实除非是repeated,否则不用写了):

  • Required: 表示是一个必须字段。(proto3弃用,不要使用了!)
  • Optional:表示是一个可选字段,可以有选择性的设置或者不设置该字段的值。(默认如此,不用声明)
  • Repeated:表示该字段可以包含0~N个元素。可以类比成一个数组的值。

声明细节:

声明的结构体可以嵌套使用:

message SearchResponse {
  Result result = 1;
}
message Result {
  string url = 1;
  string title = 2;
}

如果是内部声明的message类型,则只能内部直接使用

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
  }
  Result result = 1;
}

Enum

  • 枚举类型中必须包含值为0的元素且为第一个元素,兼容 proto2 语法。
  • 枚举常量必须在32位整型值的范围内。因为enum值是使用可变编码方式的,对负数不够高效,因此不推荐在enum中使用负数。
message SearchRequest {
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
  }
  Corpus corpus = 1;
}

Service

定义RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码

service HelloService {
  rpc Name(XXXRequest) returns (XXXResponse);
}

Map

如果你希望创建一个关联映射,protocol buffer提供了一种快捷的语法:

map<key_type, value_type> map_field = N;

其中key_type可以是任意Integer或者string类型(所以,除了floating和bytes的任意标量类型都是可以的)value_type可以是任意类型。

例如,如果你希望创建一个project的映射,每个Projecct使用一个string作为key,你可以像下面这样定义:

message SearchRequest {
  map<string, string> mp = 3;
}
  • 从序列化中解析或者融合时,如果有重复的key则后一个key不会被使用。
  • 序列化后的顺序和map迭代器的顺序是乱序的。
  • Map的字段不能被repeated修饰。
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_44626319/article/details/121431008
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢