go 1.19 gRPC的简单实操
学习一段时间的grpc了,今天简单记录下grpc的使用流程。
在进入今天的分享前,请确保自己已经安装好相关的环境:
- go 1.19
- protoc v3.19.4
- protoc-gen-go v1.28.1
- protoc-gen-go-grpc v1.3.0
1.项目目录结构
如下图显示:
2.proto 文件-接口定义
这里我们以简单的 HelloSerivce
为切入点:
syntax = "proto3"; // 固定语法前缀 option go_package = "."; // 指定生成的go代码在你项目中的导入路径 package pb; // 包名 // 定义服务 service HelloService { // SayHello 方法 rpc SayHello (HelloRequest) returns (HelloResponse) {} } // 请求消息 message HelloRequest { string name = 1; } // 响应消息 message HelloResponse { string reply = 1; }
3.生成 pb.go 文件
在定义好接口后,我们就可以生成相关的对应的go代码了:
protoc --go_out=./proto --go-grpc_out=./proto pb/hello.proto # 命令说明 - --go_out,指定 pb.go 文件生成到的目录 - --go-grpc_out,指定 grpc.pb.go 文件生成到的目录 - pb/hello.proto,指定用到的接口定义文件
一键生成后,可以看看相关的 pb.go文件:
hello.pb.go
// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v3.19.4 // source: pb/hello.proto package proto import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // 请求消息 type HelloRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } func (x *HelloRequest) Reset() { *x = HelloRequest{} if protoimpl.UnsafeEnabled { mi := &file_pb_hello_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *HelloRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*HelloRequest) ProtoMessage() {} func (x *HelloRequest) ProtoReflect() protoreflect.Message { mi := &file_pb_hello_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. func (*HelloRequest) Descriptor() ([]byte, []int) { return file_pb_hello_proto_rawDescGZIP(), []int{0} } func (x *HelloRequest) GetName() string { if x != nil { return x.Name } return "" } // 响应消息 type HelloResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Reply string `protobuf:"bytes,1,opt,name=reply,proto3" json:"reply,omitempty"` } func (x *HelloResponse) Reset() { *x = HelloResponse{} if protoimpl.UnsafeEnabled { mi := &file_pb_hello_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *HelloResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*HelloResponse) ProtoMessage() {} func (x *HelloResponse) ProtoReflect() protoreflect.Message { mi := &file_pb_hello_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use HelloResponse.ProtoReflect.Descriptor instead. func (*HelloResponse) Descriptor() ([]byte, []int) { return file_pb_hello_proto_rawDescGZIP(), []int{1} } func (x *HelloResponse) GetReply() string { if x != nil { return x.Reply } return "" } var File_pb_hello_proto protoreflect.FileDescriptor var file_pb_hello_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x70, 0x62, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70, 0x62, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x25, 0x0a, 0x0d, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x32, 0x41, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x03, 0x5a, 0x01, 0x2e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_pb_hello_proto_rawDescOnce sync.Once file_pb_hello_proto_rawDescData = file_pb_hello_proto_rawDesc ) func file_pb_hello_proto_rawDescGZIP() []byte { file_pb_hello_proto_rawDescOnce.Do(func() { file_pb_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_pb_hello_proto_rawDescData) }) return file_pb_hello_proto_rawDescData } var file_pb_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_pb_hello_proto_goTypes = []interface{}{ (*HelloRequest)(nil), // 0: pb.HelloRequest (*HelloResponse)(nil), // 1: pb.HelloResponse } var file_pb_hello_proto_depIdxs = []int32{ 0, // 0: pb.HelloService.SayHello:input_type -> pb.HelloRequest 1, // 1: pb.HelloService.SayHello:output_type -> pb.HelloResponse 1, // [1:2] is the sub-list for method output_type 0, // [0:1] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_pb_hello_proto_init() } func file_pb_hello_proto_init() { if File_pb_hello_proto != nil { return } if !protoimpl.UnsafeEnabled { file_pb_hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HelloRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_pb_hello_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HelloResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_pb_hello_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 1, }, GoTypes: file_pb_hello_proto_goTypes, DependencyIndexes: file_pb_hello_proto_depIdxs, MessageInfos: file_pb_hello_proto_msgTypes, }.Build() File_pb_hello_proto = out.File file_pb_hello_proto_rawDesc = nil file_pb_hello_proto_goTypes = nil file_pb_hello_proto_depIdxs = nil }
hello_grpc.pb.go
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 // - protoc v3.19.4 // source: pb/hello.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.32.0 or later. const _ = grpc.SupportPackageIsVersion7 const ( HelloService_SayHello_FullMethodName = "/pb.HelloService/SayHello" ) // HelloServiceClient is the client API for HelloService 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. type HelloServiceClient interface { // SayHello 方法 SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) } type helloServiceClient struct { cc grpc.ClientConnInterface } func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient { return &helloServiceClient{cc} } func (c *helloServiceClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { out := new(HelloResponse) err := c.cc.Invoke(ctx, HelloService_SayHello_FullMethodName, in, out, opts...) if err != nil { return nil, err } return out, nil } // HelloServiceServer is the server API for HelloService service. // All implementations must embed UnimplementedHelloServiceServer // for forward compatibility type HelloServiceServer interface { // SayHello 方法 SayHello(context.Context, *HelloRequest) (*HelloResponse, error) mustEmbedUnimplementedHelloServiceServer() } // UnimplementedHelloServiceServer must be embedded to have forward compatible implementations. type UnimplementedHelloServiceServer struct { } func (UnimplementedHelloServiceServer) SayHello(context.Context, *HelloRequest) (*HelloResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") } func (UnimplementedHelloServiceServer) mustEmbedUnimplementedHelloServiceServer() {} // UnsafeHelloServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to HelloServiceServer will // result in compilation errors. type UnsafeHelloServiceServer interface { mustEmbedUnimplementedHelloServiceServer() } func RegisterHelloServiceServer(s grpc.ServiceRegistrar, srv HelloServiceServer) { s.RegisterService(&HelloService_ServiceDesc, srv) } func _HelloService_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.(HelloServiceServer).SayHello(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: HelloService_SayHello_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HelloServiceServer).SayHello(ctx, req.(*HelloRequest)) } return interceptor(ctx, in, info, handler) } // HelloService_ServiceDesc is the grpc.ServiceDesc for HelloService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var HelloService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "pb.HelloService", HandlerType: (*HelloServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "SayHello", Handler: _HelloService_SayHello_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "pb/hello.proto", }
4.server
此段我们主要是定义 service
的具体业务处理逻辑,启动 grpc server
。
server
package main import ( "context" "fmt" "log" "net "go-grpc-demo1/proto" "google.golang.org/grpc" ) func main() { // create tcp lis lis, err := net.Listen("tcp", ":8093") if err != nil { panic(err) } // create grpc server server := grpc.NewServer() // register user defined service log.Printf("Register server service: %+v", HelloService{}) proto.RegisterHelloServiceServer(server, &HelloService{}) // run grpc server if err = server.Serve(lis); err != nil { panic(err) } } // 具体业务处理 type HelloService struct { proto.UnimplementedHelloServiceServer } // 具体业务实现的方法逻辑 func (s *HelloService) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloResponse, error) { log.Printf("Recv from RPC Client call, params: %s", req.Name) return &proto.HelloResponse{ Reply: fmt.Sprintf("hello name: %s", req.Name), }, nil }
写好 server 后可以启动服务,等待client的连接、请求。
附上 server
端的创建流程:
5.client
客户端代码很简单,此段代码主要根据 pb.go
、grpc.pb.go
创建 client,进行server的 Dial 请求:
package main import ( "context" "fmt" "log" "go-grpc-demo1/proto" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) func main() { // conn server conn, err := grpc.Dial("localhost:8093", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalln(err) } // call .grpc.pb.go -> NewClient(), create client client := proto.NewHelloServiceClient(conn) // call .grpc.pb.go client req method, use .pb.go file pre generate req method resp, err := client.SayHello(context.Background(), &proto.HelloRequest{ Name: "gRPC_hello", }) // print response params fmt.Printf("response: %+v", resp) }
附上 client
端的创建流程:
6.测试请求响应
先启动 server, 然后启动 client 向server发起请求:
server
2023/03/20 15:07:19 Register server service: {UnimplementedHelloServiceServer:{}}
client
response: reply:"hello name: gRPC_hello"
server
2023/03/20 15:07:19 Register server service: {UnimplementedHelloServiceServer:{}} 2023/03/20 15:08:05 Recv from RPC Client call, params: gRPC_hello
从我们定义的接口及 server
和 client
的请求响应逻辑,我们向 server
端发送带 name
参数的请求,server
端收到后打印请求参数,并返回 reply
响应,整个流程简单,但不失为go-grpc入门实操的一次尝试,更复杂的业务逻辑,更多服务治理,无非就是加功能,但这些基本流程是一样的,动起手来实践吧。
参考文档:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!