【GoLang】gRPC实现简单通信案例

大体的思路是:

1. 先新建一个proto文件,在该文件中定义好gRPC双方交互的接口

2. 依照该proto文件去生成 .go文件, 生成的.go文件中就包含了声明的交互接口

3. 使用生成的.go文件进行交互

第一步:先新建一个proto文件,在该文件中定义好gRPC双方交互的接口

syntax = "proto3";  // 定义使用的protocol buffers语法版本,目前一般都用proto3

// package 指定protobuf的命名空间,相当于包名
// 如果省略package定义,那么所有定义(Greeter、HelloRequest 等)属于全局命名空间,
// 此时若有同名定义(例如另一个文件也定义了 HelloRequest),会导致冲突。
package proto;

// 指定生成的 Go 代码的包路径和包名称。
// "./proto" 表示生成的 Go 代码将放在当前目录(greeter.proto文件所在目录),包名为 "proto"。
option go_package = "./proto";

// 定义gRPC服务,建议将服务名与 .proto 文件名保持一致
service Greeter {    
  // 定义 gRPC 交互的接口
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

// 定义 gRPC 交互的接口的参数
message HelloRequest {
  // 1表示字段编号,是唯一的,用于在二进制编码中标识字段
  string name = 1;
}

// 定义 gRPC 交互的接口的返回值
message HelloResponse {
  string message = 1;
}

先看架构,我们有一个客户端(图中client) 和 服务端(图中server), 双端之间通过图中的proto文件进行通信。

第二步:依照该proto文件去生成 .go文件, 生成的.go文件中就包含了声明的交互接口

输入命令:

protoc --go-grpc_out=. --go_out=. ./greeter.proto

protoc:Protocol Buffer编译器,用于将.proto文件编译为各种语言的代码

--go-grpc_out=.:指定生成gRPC服务代码的输出目录为当前目录

--go_out=.:指定生成普通Go结构体等的输出目录也为当前目录

./greeter.proto:要编译的.proto文件路径

接着就会生成2个文件:

greeter.pb.go 里面存放着根据 greeter.proto 中的结构体生成的结构体及对应方法,

greeter_grpc.pb.go 里面主要进行了接口的实现

其实也就是把proto文件中定义的接口函数和结构体分别分配到了两个.go文件中(如下图所示)

生成的greeter_grpc.pb.go 和 greeter.pb.go 文件的内容我放在文末了。

第三步:使用生成的.go文件进行交互

我们有了现在有了交互的接口,剩下就只需要创建客户端和服务端,让其各自调用接口进行通信即可。

 创建服务端:

服务端主要就做3件事
1.监听端口  2.创建服务端实例  3.注册具体服务

package main

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

// greeterServer 是实现GreeterServer接口的类型
type greeterServer struct {
    proto.UnimplementedGreeterServer
}

// SayHello 让greeterServer隐式实现GreeterServer接口
func (s *greeterServer) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloResponse, error) {
    return &proto.HelloResponse{Message: req.GetName()}, nil
}

// 主要就做3件事
// 1.监听端口  2.创建服务端实例  3.注册具体服务
func main() {
    // 监听端口
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
       log.Fatalf("failed to listen: %v", err)
    }

    // 创建服务端实例
    s := grpc.NewServer()

    // 注册具体服务
    proto.RegisterGreeterServer(s, &greeterServer{})
}

创建客户端:

客户端主要就做3件事:1.建立连接  2.创建客户端实例  3.调用服务端的服务

package main

import (
    "context"
    "demo2/proto"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "log"
)

// 1.建立连接  2.创建客户端实例  3.调用服务端的服务
func main() {
    // 建立连接
    // grpc.WithTransportCredentials 用于配置连接的传输安全选项
    // insecure.NewCredentials() 创建一个不安全的凭证对象,表示不使用 TLS 加密
    conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
       log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()

    // 创建客户端实例
    c := proto.NewGreeterClient(conn)

    // 调用服务端的服务
    resp, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "xun"})
    if err != nil {
       log.Fatalf("could not greet: %v", err)
    }

    fmt.Println("1", resp)
}

接着启动服务端,再启动客户端,查看运行结果:

greeter.pb.go:

// 生成的请求结构体
type HelloRequest struct {
    // 这是一个内部字段,用于跟踪消息的序列化状态。
    state protoimpl.MessageState `protogen:"open.v1"`

    // .proto文件定义的HelloRequest中的字段
    Name          string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    // bytes:表示字段的底层编码类型,protobuf 中的 string 被编码为字节序列(UTF-8)。
    //1:字段编号,在二进制编码中唯一标识这个字段(与 .proto 文件中的 name = 1 对应)。
    //opt:表示字段是可选的(optional),在 proto3 中所有字段默认都是可选的。
    //name=name:protobuf 字段名(左边的 name)映射到 Go 字段名(右边的 Name),这里大小写不同是因为 Go 的命名规范。
    //proto3:表示使用 proto3 语法,影响默认值行为(例如,空字符串为默认值)。

    // 这是一个内部字段,用于存储未知字段的信息。
    unknownFields protoimpl.UnknownFields

    // 这是一个内部字段,用于缓存消息的大小。
    sizeCache     protoimpl.SizeCache
}



// 结构体的各个方法
// 将 HelloRequest 的所有字段重置为默认值。
func (x *HelloRequest) Reset() {
    *x = HelloRequest{}
    mi := &file_proto_greeter_proto_msgTypes[0]
    ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
    ms.StoreMessageInfo(mi)
}

// 返回一个格式化的字符串,包含字段名和值,例如 "HelloRequest{Name:World}"。
func (x *HelloRequest) String() string {
    return protoimpl.X.MessageStringOf(x)
}

// 标记 HelloRequest 实现了 proto.Message 接口。 通常是一个空方法,仅用于满足接口要求。
func (*HelloRequest) ProtoMessage() {}


func (x *HelloRequest) ProtoReflect() protoreflect.Message {
    mi := &file_proto_greeter_proto_msgTypes[0]
    if x != nil {
       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
       if ms.LoadMessageInfo() == nil {
          ms.StoreMessageInfo(mi)
       }
       return ms
    }
    return mi.MessageOf(x)
}

// 返回 HelloRequest 的 protobuf 描述符信息。在需要反射操作(例如动态解析消息)时使用,通常开发者不直接调用。
func (*HelloRequest) Descriptor() ([]byte, []int) {
    return file_proto_greeter_proto_rawDescGZIP(), []int{0}
}


// GetXXX表示获取在.proto文件中声明的字段XXX的值
// 这里表示获取 Name 字段的值,提供安全的访问方式。
func (x *HelloRequest) GetName() string {
    if x != nil {
       return x.Name
    }
    return ""
}

greeter_grpc.pb.go:

// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc             v5.26.1
// source: proto/greeter.proto

// 指定protobuf的命名空间,相当于proto模块下的包名

package proto

import (
    context "context"
    grpc "google.golang.org/grpc"
    codes "google.golang.org/grpc/codes"
    status "google.golang.org/grpc/status"
)

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9

const (
    Greeter_SayHello_FullMethodName = "/proto.Greeter/SayHello"
)

// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// 定义Greeter服务,表示这是一个gRPC服务
type GreeterClient interface {
    SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)
}

type greeterClient struct {
    cc grpc.ClientConnInterface
}

func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
    return &greeterClient{cc}
}

func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) {
    cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
    out := new(HelloResponse)
    err := c.cc.Invoke(ctx, Greeter_SayHello_FullMethodName, in, out, cOpts...)
    if err != nil {
       return nil, err
    }
    return out, nil
}

// GreeterServer is the server API for Greeter service.
// All implementations should embed UnimplementedGreeterServer
// for forward compatibility.
//
// 定义Greeter服务,表示这是一个gRPC服务。服务端必须实现这个接口,提供 SayHello 的具体逻辑。
type GreeterServer interface {
    SayHello(context.Context, *HelloRequest) (*HelloResponse, error)
}

// UnimplementedGreeterServer should be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedGreeterServer struct{}

func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloResponse, error) {
    return nil, status.Errorf(codes.Unimplemented, " method SayHello not implemented")
}
func (UnimplementedGreeterServer) testEmbeddedByValue() {}

// UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GreeterServer will
// result in compilation errors.
type UnsafeGreeterServer interface {
    mustEmbedUnimplementedGreeterServer()
}

func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) {
    // If the following call pancis, it indicates UnimplementedGreeterServer was
    // embedded by pointer and is nil.  This will cause panics if an
    // unimplemented method is ever invoked, so we test this at initialization
    // time to prevent it from happening at runtime later due to I/O.
    if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
       t.testEmbeddedByValue()
    }
    s.RegisterService(&Greeter_ServiceDesc, srv)
}

func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    in := new(HelloRequest)
    if err := dec(in); err != nil {
       return nil, err
    }
    if interceptor == nil {
       return srv.(GreeterServer).SayHello(ctx, in)
    }
    info := &grpc.UnaryServerInfo{
       Server:     srv,
       FullMethod: Greeter_SayHello_FullMethodName,
    }
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
       return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
    }
    return interceptor(ctx, in, info, handler)
}

// Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Greeter_ServiceDesc = grpc.ServiceDesc{
    ServiceName: "proto.Greeter",
    HandlerType: (*GreeterServer)(nil),
    Methods: []grpc.MethodDesc{
       {
          MethodName: "SayHello",
          Handler:    _Greeter_SayHello_Handler,
       },
    },
    Streams:  []grpc.StreamDesc{},
    Metadata: "proto/greeter.proto",
}

四个文件的关系:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值