大体的思路是:
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",
}
四个文件的关系: