lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
 
  1. pb开发-proto文件 以及 PB在 go项目中的开发过程

 
以下是一个简单的示例,演示如何在Go项目中使用PB进行开发:
  1. 编写Proto文件
假设我们要开发一个简单的分布式计算系统。首先,我们需要编写一个Proto文件来定义请求和响应消息类型以及服务接口。以下是一个示例:
复制代码
syntax = "proto3";

message ComputeRequest {
  int32 a = 1;
  int32 b = 2;
}

message ComputeResponse {
  int32 result = 1;
}

service Calculator {
  rpc Add(ComputeRequest) returns (ComputeResponse) {}
}
该文件定义了两个消息类型 ComputeRequestComputeResponse,以及一个名为 Calculator 的服务接口,该接口包含一个 Add 方法,用于将两个整数相加并返回结果。
  1. 生成Go代码
在命令行中运行以下命令,使用protoc-gen-go插件将Proto文件编译为Go代码:
复制代码
protoc --go_out=. calculator.proto
这将在当前目录中生成一个名为 calculator.pb.go 的文件,其中包含所有通过Proto文件定义的消息类型和服务接口。
  1. 实现服务端
在服务器端,我们需要实现定义的RPC服务。为此,我们需要创建一个实现接口的结构体,并编写处理请求和生成响应的代码。以下是一个示例:
go复制代码
package main

import (
        "context""log""net""google.golang.org/grpc"

        pb "path/to/calculator.pb"
)

type server struct{}

func (s *server) Add(ctx context.Context, req *pb.ComputeRequest) (*pb.ComputeResponse, error) {
        result := req.A + req.B
        return &pb.ComputeResponse{Result: result}, nil
}

func main() {
        lis, err := net.Listen("tcp", ":8080")
        if err != nil {
                log.Fatalf("failed to start server: %v", err)
        }

        srv := grpc.NewServer()
        pb.RegisterCalculatorServer(srv, &server{})

        log.Printf("server listening on %s", lis.Addr().String())
        if err := srv.Serve(lis); err != nil {
                log.Fatalf("failed to serve: %v", err)
        }
}
在上面的示例中,我们定义了一个名为 Server 的结构体,并实现了 Add 方法,该方法接收一个 ComputeRequest 请求对象并返回一个 ComputeResponse 响应对象,该响应对象包含两个数字的总和。然后,我们创建一个 grpc.Server 对象,并向其注册 Calculator 服务。最后,我们启动服务器并开始监听传入的连接。
  1. 实现客户端
在客户端上,我们需要编写代码来使用通过Proto文件生成的客户端类与服务器进行通信。以下是一个示例:
go复制代码
package main

import (
        "context""log""time""google.golang.org/grpc"

        pb "path/to/calculator.pb"
)

func main() {
        conn, err := grpc.Dial(":8080", grpc.WithInsecure())
        if err != nil {
                log.Fatalf("failed to connect: %v", err)
        }
        defer conn.Close()

        c := pb.NewCalculatorClient(conn)

        req := &pb.ComputeRequest{A: 10, B: 20}
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
        defer cancel()

        res, err := c.Add(ctx, req)
        if err != nil {
                log.Fatalf("failed to add: %v", err)
        }

        log.Printf("result: %d", res.Result)
}
在上面的示例中,我们使用 grpc.Dial() 连接到服务器,并使用通过Proto文件生成的 NewCalculatorClient() 函数创建一个客户端对象。然后,我们创建一个 ComputeRequest 请求对象,并使用 context.WithTimeout() 创建一个有超时限制的上下文。最后,我们调用客户端的 Add 方法,并打印响应。
  1. 测试和调试
在开发过程中,我们需要对我们的代码进行测试和调试,以确保它能够正确地序列化和反序列化数据,并且客户端和服务器可以正常通信。
 
 
 
  1. GRPC 结合实例 说明 其使用及原理

以下是一个示例以说明gRPC的使用及原理。
我们假设需要在分布式系统中实现排序功能,可以使用gRPC来定义服务接口,并通过该接口使客户端向服务器发送请求,服务器对接收到的数组进行排序并返回已排序的结果。以下是Proto文件示例:
protobuf复制代码
syntax = "proto3";

message SortRequest {
  repeated int32 nums = 1;
}

message SortResponse {
  repeated int32 sorted_nums = 1;
}

service Sorter {
  rpc Sort(SortRequest) returns (SortResponse) {}
}
上述Proto文件定义了两个消息类型 SortRequestSortResponse,以及一个名为 Sorter 的服务接口,该接口包含一个 Sort 方法,用于对传入的整数数组进行排序并返回已排序的数组。
根据这个Proto文件,我们可以使用protoc编译器生成Go语言的代码,如下所示:
bash复制代码
protoc --go_out=. sorter.proto
执行以上命令会在当前目录下生成一个名为 sorter.pb.go 的文件,其中包含所有通过Proto文件定义的消息类型和服务接口。
接下来,我们需要编写一个服务器端程序,用于实现定义的 Sorter 服务接口。以下是示例代码:
go复制代码
package main

import (
        "context""log""net""google.golang.org/grpc"

        pb "path/to/sorter.pb"
)

type server struct{}

func (s *server) Sort(ctx context.Context, req *pb.SortRequest) (*pb.SortResponse, error) {
        nums := req.GetNums()
        // 使用快排算法对整数数组进行排序
        quickSort(nums, 0, len(nums)-1)
        return &pb.SortResponse{SortedNums: nums}, nil
}

func quickSort(nums []int32, left, right int) {
        if left >= right {
                return
        }
        pivot := partition(nums, left, right)
        quickSort(nums, left, pivot-1)
        quickSort(nums, pivot+1, right)
}

func partition(nums []int32, left, right int) int {
        pivot := nums[right]
        i := left
        for j := left; j < right; j++ {
                if nums[j] < pivot {
                        nums[i], nums[j] = nums[j], nums[i]
                        i++
                }
        }
        nums[i], nums[right] = nums[right], nums[i]
        return i
}

func main() {
        lis, err := net.Listen("tcp", ":8080")
        if err != nil {
                log.Fatalf("failed to start server: %v", err)
        }

        srv := grpc.NewServer()
        pb.RegisterSorterServer(srv, &server{})

        log.Printf("server listening on %s", lis.Addr().String())
        if err := srv.Serve(lis); err != nil {
                log.Fatalf("failed to serve: %v", err)
        }
}
在以上示例中,我们首先定义了一个名为 Server 的结构体,并实现了 Sort 方法,该方法接收一个 SortRequest 请求对象,并对数组进行排序。然后,我们创建一个 grpc.Server 对象,并向其注册 Sorter 服务。最后,我们启动服务器并开始监听传入的连接。
现在,我们需要编写一个客户端程序,用于向服务器发送请求并获取响应。以下是示例代码:
go复制代码
package main

import (
        "context""log""google.golang.org/grpc"

        pb "path/to/sorter.pb"
)

func main() {
        conn, err := grpc.Dial(":8080", grpc.WithInsecure())
        if err != nil {
                log.Fatalf("failed to connect: %v", err)
        }
        defer conn.Close()

        c := pb.NewSorterClient(conn)

        req := &pb.SortRequest{Nums: []int32{9, 3, 5, 2, 6, 4, 8, 7, 1}}
        res, err := c.Sort(context.Background(), req)
        if err != nil {
                log.Fatalf("failed to sort: %v", err)
        }

        log.Printf("sorted nums: %v", res.SortedNums)
}
以上代码是一个gRPC客户端程序的示例,其主要流程如下:
  1. 使用 grpc.Dial 函数连接服务器,并返回一个连接对象 conn
  2. 创建一个 SorterClient 对象 c,用于调用 Sort 方法。这个对象由 pb.NewSorterClient(conn) 创建,其中 pb 是通过 protoc 编译器生成的Go语言代码包。
  3. 创建一个 SortRequest 对象 req,将要排序的整数数组作为参数传递给 Sort 方法。
  4. 调用 Sort 方法,并传递请求对象和上下文信息作为参数,获取响应结果和错误信息。
  5. 在获取到响应结果后,打印已排序的整数数组。
在调用 Sort 方法时,gRPC会自动处理序列化和反序列化,以及TCP连接的管理等底层细节,这样我们就可以专注于定义服务接口和业务逻辑,并使用gRPC轻松地构建分布式应用程序。
  1. 总结 关于grpc 客户端调用步骤有哪些 都做了什么事

以下是基于前面的示例代码对gRPC客户端调用步骤及其所做的工作进行的详细总结:
  1. 创建 grpc.ClientConn 对象连接服务器。在本例中,使用了 grpc.Dial 函数,指定了服务器地址和连接选项(WithInsecure() 表示不使用TLS安全连接)。
  2. 使用 pb.NewSorterClient(conn) 函数创建一个 SorterClient 对象,其中 pb 是通过 Protobuf 编译器生成的包含服务接口和消息的Go语言代码包。该客户端对象表示与服务器交互的实体。
  3. 创建一个 SortRequest 对象 req 作为请求消息,将要排序的整数数组作为参数传递给 Sort 方法。
  4. 调用 c.Sort(context.Background(), req) 方法,向服务器发送请求消息并获取响应信息。在这里,我们传递了上下文信息 context.Background()和请求消息 req 作为参数,并从 SorterClient 对象 c 中调用了 Sort 方法,该方法与定义的服务接口相匹配。
  5. gRPC处理序列化和反序列化,以及TCP连接的管理等底层细节。它将请求消息从结构化数据转换为二进制格式,并将其发送到服务器。然后,它接收来自服务器的响应消息,并将其从二进制格式转换为结构化数据。在此过程中,我们无需关心底层通信协议。
  6. 处理RPC调用的上下文信息,如超时时间、取消请求等。在这个例子中,使用了默认上下文信息 context.Background()
  7. 处理元数据(metadata),如身份验证令牌等。在这个例子中,没有使用自定义元数据。
  8. 处理错误信息,并在需要时重试请求。如果出现错误,gRPC将返回错误信息,并可以根据错误类型和错误码进行处理。在这个例子中,如果有错误,则会在控制台上打印错误信息并停止程序。
  9. 打印已排序的整数数组。如果一切顺利,我们将获得一个包含已排序的整数数组的响应结果。
综上所述,gRPC客户端调用主要涉及与服务器的连接、消息序列化和反序列化,上下文信息和元数据处理,错误处理等方面的工作。通过封装这些复杂的操作和细节,gRPC能够提供简单但强大的方式使开发人员创建分布式系统。
以下是基于前面的示例代码对gRPC客户端调用步骤及其所做的工作进行的详细总结:
  1. 创建 grpc.ClientConn 对象连接服务器。在本例中,使用了 grpc.Dial 函数,指定了服务器地址和连接选项(WithInsecure() 表示不使用TLS安全连接)。
  2. 使用 pb.NewSorterClient(conn) 函数创建一个 SorterClient 对象,其中 pb 是通过 Protobuf 编译器生成的包含服务接口和消息的Go语言代码包。该客户端对象表示与服务器交互的实体。
  3. 创建一个 SortRequest 对象 req 作为请求消息,将要排序的整数数组作为参数传递给 Sort 方法。
  4. 调用 c.Sort(context.Background(), req) 方法,向服务器发送请求消息并获取响应信息。在这里,我们传递了上下文信息 context.Background()和请求消息 req 作为参数,并从 SorterClient 对象 c 中调用了 Sort 方法,该方法与定义的服务接口相匹配。
  5. gRPC处理序列化和反序列化,以及TCP连接的管理等底层细节。它将请求消息从结构化数据转换为二进制格式,并将其发送到服务器。然后,它接收来自服务器的响应消息,并将其从二进制格式转换为结构化数据。在此过程中,我们无需关心底层通信协议。
  6. 处理RPC调用的上下文信息,如超时时间、取消请求等。在这个例子中,使用了默认上下文信息 context.Background()
  7. 处理元数据(metadata),如身份验证令牌等。在这个例子中,没有使用自定义元数据。
  8. 处理错误信息,并在需要时重试请求。如果出现错误,gRPC将返回错误信息,并可以根据错误类型和错误码进行处理。在这个例子中,如果有错误,则会在控制台上打印错误信息并停止程序。
  9. 打印已排序的整数数组。如果一切顺利,我们将获得一个包含已排序的整数数组的响应结果。
综上所述,gRPC客户端调用主要涉及与服务器的连接、消息序列化和反序列化,上下文信息和元数据处理,错误处理等方面的工作。通过封装这些复杂的操作和细节,gRPC能够提供简单但强大的方式使开发人员创建分布式系统。
以下是基于前面的示例代码对gRPC客户端调用步骤及其所做的工作进行的详细总结:
  1. 创建 grpc.ClientConn 对象连接服务器。在本例中,使用了 grpc.Dial 函数,指定了服务器地址和连接选项(WithInsecure() 表示不使用TLS安全连接)。
  2. 使用 pb.NewSorterClient(conn) 函数创建一个 SorterClient 对象,其中 pb 是通过 Protobuf 编译器生成的包含服务接口和消息的Go语言代码包。该客户端对象表示与服务器交互的实体。
  3. 创建一个 SortRequest 对象 req 作为请求消息,将要排序的整数数组作为参数传递给 Sort 方法。
  4. 调用 c.Sort(context.Background(), req) 方法,向服务器发送请求消息并获取响应信息。在这里,我们传递了上下文信息 context.Background()和请求消息 req 作为参数,并从 SorterClient 对象 c 中调用了 Sort 方法,该方法与定义的服务接口相匹配。
  5. gRPC处理序列化和反序列化,以及TCP连接的管理等底层细节。它将请求消息从结构化数据转换为二进制格式,并将其发送到服务器。然后,它接收来自服务器的响应消息,并将其从二进制格式转换为结构化数据。在此过程中,我们无需关心底层通信协议。
  6. 处理RPC调用的上下文信息,如超时时间、取消请求等。在这个例子中,使用了默认上下文信息 context.Background()
  7. 处理元数据(metadata),如身份验证令牌等。在这个例子中,没有使用自定义元数据。
  8. 处理错误信息,并在需要时重试请求。如果出现错误,gRPC将返回错误信息,并可以根据错误类型和错误码进行处理。在这个例子中,如果有错误,则会在控制台上打印错误信息并停止程序。
  9. 打印已排序的整数数组。如果一切顺利,我们将获得一个包含已排序的整数数组的响应结果。
综上所述,gRPC客户端调用主要涉及与服务器的连接、消息序列化和反序列化,上下文信息和元数据处理,错误处理等方面的工作。通过封装这些复杂的操作和细节,gRPC能够提供简单但强大的方式使开发人员创建分布式系统。
 
 
  1. Proto文件 基本的语法

 
 
Proto文件是gRPC中用来定义服务接口和消息结构的文件,其基本语法如下:
  1. 使用 syntax 关键字指定Proto文件所使用的语法版本。目前最新的版本是proto3,可以使用以下语句指定:
protobuf复制代码
syntax = "proto3";
  1. 使用 package 关键字指定当前Proto文件所属的包名。包名应该采用反向DNS格式,并以分号结尾。例如:
protobuf复制代码
package com.example.grpc;
  1. 使用 message 关键字定义一个消息类型。一个消息类型可以包含多个字段,每个字段都有一个名称、类型和唯一的数字标识符。例如:
protobuf复制代码
message Person {
    string name = 1;
    int32 age = 2;
    bool is_student = 3;
}
  1. 使用 repeated 关键字定义一个重复字段。重复字段可以出现零次或多次,并且会按照添加的顺序进行编码和解码。例如:
protobuf复制代码
message Book {
    string title = 1;
    repeated string author = 2;
}
 
  1. 使用 enum 关键字定义一个枚举类型。枚举类型包含多个命名的常量,每个常量都有一个名称和唯一的数字值。例如:
protobuf复制代码
enum Gender {
    UNKNOWN = 0;
    MALE = 1;
    FEMALE = 2;
}
  1. 使用 使用上述是Proto文件中最基本的语法,通过这些语法我们可以定义自己的服务接口和消息结构,从而实现分布式系统间的通信。在实际开发中,可能还会涉及到嵌套类型、子消息、扩展等高级语法。
  1. proto文件中不指定 syntax

如果在Proto文件中没有指定 syntax 关键字,gRPC会假设使用的是proto2语法,并默认采用一些proto2的规则和特性。这可能会导致与proto3语法不兼容的问题,因此建议始终在Proto文件中明确指定所使用的语法版本。
在Proto3语法中,一些proto2特性已被取消或更改,而新的特性也已被引入。例如:
  • 在proto3语法中,所有字段都必须显式地分配一个数字标识符。
  • proto3不再支持required关键字,因为它对于可选字段和必需字段之间的区别没有太大意义。
  • proto3不再支持默认值,因为它可以导致歧义和错误。
  • 在proto3语法中,枚举类型的第一个常量必须具有值0,以避免混淆和错误。
因此,如果未在Proto文件中指定 syntax 关键字,则可能会导致解析和编码出现问题。因此,在编写Proto文件时,应该始终指定正在使用的语法版本,以便在不同的平台和语言中实现正确的通信。
 
 
  1. proto文件中 的repeated 在GO语言中是代表 集合吗

 
是的,在Go语言中,与集合、列表或数组等数据结构类似。
在Protobuf编译器生成的Go代码中,对于使用了 repeated 关键字定义的字段,会被转换为相应类型的切片(slice),例如:
protobuf复制代码
message Person {
    string name = 1;
    int32 age = 2;
    repeated string email = 3;
}
在这个例子中,Person 消息类型有三个字段:nameageemail。其中 email 字段使用 repeated 关键字定义,表示一个人可以有零个或多个邮件地址。当使用 Protobuf 编译器生成 Go 代码时,将会自动生成具有以下签名的方法:
go复制代码
func (m *Person) GetEmail() []string
该方法返回一个字符串类型的切片,其中包含了所有的 email 字段值。
因此,repeated 关键字在Go语言中确实代表了一种集合/列表/数组类型,并且可以用切片来表示和访问。这种方式可以方便我们处理消息中包含多个值的情况,并且在不同的平台和语言之间实现兼容性。
 
  1. proto文件中 optional 关键字

在最新的Proto3语法中,optional 关键字已被废弃,不再被支持。在之前的Proto2语法中,
例如,在Proto2语法中,可以使用 optional 关键字来定义一个可选字段:
protobuf复制代码
message Person {
    optional string email = 1;
    // ...
}
然而,这种方式虽然在语言层面上提供了一些便利,但在实际使用中可能会引起歧义和错误。因此,在Proto3语法中,optional 关键字已被取消,所有字段都默认为可选的,即可以包含零个或一个值。
  • proto文件中service 中的 option关键字

在Proto文件中,
option 关键字用于为服务(service)或方法(method)指定附加选项。
这些选项可以影响代码生成、运行时行为和通信协议等方面。
在服务(service)级别上,可以使用 option 关键字来指定以下选项:
  • rpc.greeter:用于指定RPC方法的名称前缀。例如,如果将此选项设置为“Hello”,则所有RPC方法的名称前缀都会自动添加“Hello”。
  • cc_enable_arenas:控制是否启用内存分配器(Memory Arena Allocator)。默认情况下,gRPC使用内存分配器来管理消息大小和生命周期,以提高性能。但是,对于某些平台和应用程序,可能需要禁用内存分配器。通过设置此选项,可以灵活地控制内存分配器的使用。
 
例如,以下是一个使用 option 关键字定义服务级别选项的例子:
protobuf复制代码
service MyService {
    option cc_enable_arenas = false;

    rpc MyRpcMethod(MyRequest) returns(MyResponse) {}
}
在这个例子中,
我们使用 option 关键字在 MyService 服务中指定了一个选项:禁用内存分配器。
这将使gRPC在处理消息时不再使用内存分配器,从而提高代码兼容性和灵活性。
当然,在实际开发中,还可以根据需要在方法(method)级别上指定选项,
例如,可以使用 option 关键字指定每个方法的超时时间、重试次数、最大请求数等等。
这些选项可以提高系统的稳定性和可靠性,同时也为开发人员提供了更多的灵活性和自由度。
 
 
请解读一下 以下代码中的option 做了什么 service EmailConfigSvr { option (srpc.service_option_id) = 0x8628; //添加邮件配置 rpc AddEmailConfig(AddEmailConfigReq) returns (AddEmailConfigRsp) { option (srpc.method_option_id) = 0x01; } //更新邮件配置 rpc UpdateEmailConfig(UpdateEmailConfigReq) returns (UpdateEmailConfigRsp) { option (srpc.method_option_id) = 0x02; } //删除邮件配置 rpc DelEmailConfig(DelEmailConfigReq) returns (DelEmailConfigRsp) { option (srpc.method_option_id) = 0x03; } //批量查询邮箱配置 rpc BatchQueryEmailConfig (QueryEmailConfigReq) returns (QueryEmailConfigRsp) { option (srpc.method_option_id) = 0x04; } }
 
在这段代码中,option 关键字用于定义了 srpc.service_option_idsrpc.method_option_id 两个选项,它们会影响生成的 gRPC 代码的行为。具体来说:
  • srpc.service_option_id:该选项定义了服务(service)级别的选项 ID,值为 0x8628。这个选项可以用来标识当前服务的版本、权限等信息,在运行时被解析和使用。例如,在微服务架构中,可能需要使用此选项指示服务的负载均衡策略、调用频率限制等信息。
  • srpc.method_option_id:该选项定义了方法(method)级别的选项 ID,分别对应四个 RPC 方法的不同取值:0x010x020x030x04。这些选项可以用来控制每个方法的行为、参数验证规则、超时时间等等。例如,AddEmailConfig 方法的选项是 0x01,表示该方法的行为是添加邮件配置,其他方法的选项与之类似。
通过使用 option 关键字定义这些选项,我们可以在Proto文件中指定更多的属性和元数据,以便在生成gRPC代码时进行处理和使用。一些选项可以用于控制编码/解码过程、调试/追踪数据流、优化网络传输等方面,从而提高系统性能和可维护性。
 
option 可以做哪些事
 
option 关键字可以用于为Proto文件中的服务(service)或方法(method)定义元数据,从而影响代码生成、运行时行为和通信协议等方面。虽然选项并不直接影响消息的结构或语义,但它们提供了一种灵活和可扩展的方式来增加附加信息和属性。
具体来说,option 可以用于以下场景:
  1. 控制代码生成:使用 option 关键字可以控制编译器生成代码的方式和输出形式。例如,可以使用 gogoproto.jsontag 选项来定义JSON标签,以控制序列化输出的格式;或者使用 go_package 选项指定生成Go代码时的包名和路径。
  2. 定义元数据:使用 option 关键字可以为服务或方法定义元数据,例如版本号、权限、调用频率限制等信息。这些元数据可以在运行时被解析和使用,以便为系统提供更多的控制和保障。
  3. 指示运行时行为:使用 option 关键字可以指示运行时行为和规则,例如超时时间、请求重试次数、TLS设置等。这些选项可以用于优化网络传输、提高系统稳定性和可靠性等方面,从而满足不同业务需求。
  4. 扩展Protobuf语法:使用 option 关键字可以扩展Protobuf语法,添加新的类型、操作符和语法规则。这些扩展可以通过自定义插件或编写DSL脚本来实现,并与其他工具集成,从而提高代码质量和效率。
总之,option 关键字是一个十分强大和灵活的特性,在Proto文件的设计和实现过程中都有着广泛的应用和价值。开发人员可以根据需要自由地定义和使用选项,以适应不同的场景和需求。
posted on 2023-09-25 16:30  白露~  阅读(97)  评论(0编辑  收藏  举报