准备工作

protoc

下载protobuf的编译器protoc,地址:https://github.com/google/protobuf/releases

Golang protobuf插件

Github项目地址:golang/protobuf,执行下面的下载安装命令:

  1. $go version
  2. go version go1.15.8 darwin/amd64
  3. $go get github.com/golang/protobuf/protoc-gen-go

编译后会安装protoc-gen-go$GOBIN目录, 默认在 $GOPATH/bin. 该目录必须在系统的环境变量$PATH中,这样在编译.proto文件时protocol编译器才能找到插件。
否则在执行命令的时候出现如下错误信息

  1. protoc --go_out=plugins=grpc:. ./*.proto
  2. protoc-gen-go: program not found or is not executable
  3. Please specify a program using absolute path or make sure the program is available in your PATH system variable
  4. --go_out: protoc-gen-go: Plugin failed with status code 1.

临时加入系统环境变量中

  1. $ export PATH="$PATH:$(go env GOPATH)/bin"

protoc命令

  1. protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto

这里详细介绍golang的编译姿势:

  • -I 参数:指定import路径,可以指定多个-I参数,编译时按顺序查找,不指定时默认查找当前目录
  • --go_out:设置所生成的Go代码输出目录,支持以下参数
    • plugins=plugin1+plugin2 - 指定插件,支持grpc,即:plugins=grpc,加载protoc-gen-go插件
    • M 参数 - 指定导入的.proto文件路径编译后对应的golang包名(不指定本参数默认就是.proto文件中import语句的路径)
    • import_prefix=xxx - 为所有import路径添加前缀,主要用于编译子目录内的多个proto文件,这个参数按理说很有用,尤其适用替代一些情况时的M参数,但是实际使用时有个蛋疼的问题导致并不能达到我们预想的效果,自己尝试看看吧
    • import_path=foo/bar - 用于指定未声明packagego_package的文件的包名,最右面的斜线前的字符会被忽略
    • 末尾 :输出文件路径 .(点号)表示输出到当前目录,生成的文件以.pb.go为文件后缀。

完整示例:

  1. protoc -I . --go_out=plugins=grpc,Mfoo/bar.proto=bar,import_prefix=foo/,import_path=foo/bar:. ./*.proto

新版本的格式已经换成了

  1. $ protoc --go_out=. --go_opt=paths=source_relative \
  2. --go-grpc_out=. --go-grpc_opt=paths=source_relative \
  3. proto/*.proto

数据类型

数组

repeated,那么该字段可以重复任意次(包括零次),重复值的顺序将保留在Protobuf中,重复字段可被视为动态大小的数组

  1. syntax = "proto3";
  2. package hello;
  3. message UserRequest{
  4. repeated string name = 1;
  5. }

类型嵌套

  1. syntax = "proto3";
  2. package hello;
  3. message Class {
  4. int32 ClassID = 1;
  5. string ClassName = 2;
  6. }
  7. message Student{
  8. Class class = 1;
  9. string name = 2;
  10. }

枚举

注意在proto3中,枚举类型必须从0开始

  1. syntax = "proto3";
  2. package hello;
  3. enum MemberType {
  4. Free = 0;
  5. Senior = 2;
  6. Gold =3;
  7. }
  8. message Customer{
  9. MemberType member =1;
  10. string name = 2;
  11. }

map

  1. syntax = "proto3";
  2. package hello;
  3. message Customer{
  4. map <string, string> name = 1;
  5. }

Helloworld

在rpc/user_pb 目录下创建一个user.proto文件

  1. syntax = "proto3";
  2. package user_pb;
  3. message UserInfo {
  4. int32 Id = 1;
  5. string Name = 2;
  6. string Password = 3;
  7. }

执行protoc 生成go数据结构文件

  1. protoc --go_out=plugins=grpc:. user.proto

user.pb.go文件内容如下

  1. // Code generated by protoc-gen-go. DO NOT EDIT.
  2. // source: user.proto
  3. package user_pb
  4. import (
  5. fmt "fmt"
  6. proto "github.com/golang/protobuf/proto"
  7. math "math"
  8. )
  9. // Reference imports to suppress errors if they are not otherwise used.
  10. var _ = proto.Marshal
  11. var _ = fmt.Errorf
  12. var _ = math.Inf
  13. // This is a compile-time assertion to ensure that this generated file
  14. // is compatible with the proto package it is being compiled against.
  15. // A compilation error at this line likely means your copy of the
  16. // proto package needs to be updated.
  17. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
  18. type UserInfo struct {
  19. Id int32 `protobuf:"varint,1,opt,name=Id,proto3" json:"Id,omitempty"`
  20. Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
  21. Password string `protobuf:"bytes,3,opt,name=Password,proto3" json:"Password,omitempty"`
  22. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  23. XXX_unrecognized []byte `json:"-"`
  24. XXX_sizecache int32 `json:"-"`
  25. }
  26. func (m *UserInfo) Reset() { *m = UserInfo{} }
  27. func (m *UserInfo) String() string { return proto.CompactTextString(m) }
  28. func (*UserInfo) ProtoMessage() {}
  29. func (*UserInfo) Descriptor() ([]byte, []int) {
  30. return fileDescriptor_116e343673f7ffaf, []int{0}
  31. }
  32. func (m *UserInfo) XXX_Unmarshal(b []byte) error {
  33. return xxx_messageInfo_UserInfo.Unmarshal(m, b)
  34. }
  35. func (m *UserInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
  36. return xxx_messageInfo_UserInfo.Marshal(b, m, deterministic)
  37. }
  38. func (m *UserInfo) XXX_Merge(src proto.Message) {
  39. xxx_messageInfo_UserInfo.Merge(m, src)
  40. }
  41. func (m *UserInfo) XXX_Size() int {
  42. return xxx_messageInfo_UserInfo.Size(m)
  43. }
  44. func (m *UserInfo) XXX_DiscardUnknown() {
  45. xxx_messageInfo_UserInfo.DiscardUnknown(m)
  46. }
  47. var xxx_messageInfo_UserInfo proto.InternalMessageInfo
  48. func (m *UserInfo) GetId() int32 {
  49. if m != nil {
  50. return m.Id
  51. }
  52. return 0
  53. }
  54. func (m *UserInfo) GetName() string {
  55. if m != nil {
  56. return m.Name
  57. }
  58. return ""
  59. }
  60. func (m *UserInfo) GetPassword() string {
  61. if m != nil {
  62. return m.Password
  63. }
  64. return ""
  65. }
  66. func init() {
  67. proto.RegisterType((*UserInfo)(nil), "user_pb.UserInfo")
  68. }
  69. func init() { proto.RegisterFile("user.proto", fileDescriptor_116e343673f7ffaf) }
  70. var fileDescriptor_116e343673f7ffaf = []byte{
  71. // 110 bytes of a gzipped FileDescriptorProto
  72. 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x2d, 0x4e, 0x2d,
  73. 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x07, 0xb1, 0xe3, 0x0b, 0x92, 0x94, 0xbc, 0xb8,
  74. 0x38, 0x42, 0x8b, 0x53, 0x8b, 0x3c, 0xf3, 0xd2, 0xf2, 0x85, 0xf8, 0xb8, 0x98, 0x3c, 0x53, 0x24,
  75. 0x18, 0x15, 0x18, 0x35, 0x58, 0x83, 0x98, 0x3c, 0x53, 0x84, 0x84, 0xb8, 0x58, 0xfc, 0x12, 0x73,
  76. 0x53, 0x25, 0x98, 0x14, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x21, 0x29, 0x2e, 0x8e, 0x80, 0xc4,
  77. 0xe2, 0xe2, 0xf2, 0xfc, 0xa2, 0x14, 0x09, 0x66, 0xb0, 0x38, 0x9c, 0x9f, 0xc4, 0x06, 0x36, 0xdb,
  78. 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x10, 0xed, 0x90, 0xc8, 0x69, 0x00, 0x00, 0x00,
  79. }

main.go 文件

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/baxiang/go-note/rpc/user_pb"
  5. "github.com/golang/protobuf/proto"
  6. )
  7. func main() {
  8. u := &user_pb.UserInfo{Id: 1001,Name:"ming",Password:"123456"}
  9. pbData,err := proto.Marshal(u)
  10. if err != nil {
  11. fmt.Println("Marshaling error: ", err)
  12. }
  13. user :=&user_pb.UserInfo{}
  14. err = proto.Unmarshal(pbData, user)
  15. if err != nil {
  16. fmt.Println("Unmarshal error: ", err)
  17. }
  18. fmt.Println(user)
  19. }

执行程序

  1. $ go run main.go
  2. Id:1001 Name:"ming" Password:"123456"

gRPC 入门1

  1. // 版本号
  2. syntax = "proto3";
  3. // 指定生成 user.pb.go的包名
  4. package proto;
  5. // 定义客户端请求的数据格式
  6. message UserRequest{
  7. // 定义请求参数,1 表示编码序号,也就是用1可以调用到这个字段,建议控制在1~15内
  8. string name = 1;
  9. }
  10. // 定义服务端响应的数据格式
  11. message UserResponse{
  12. // 定义响应参数,int32定义类型,1 表示编码序号
  13. int32 id = 1;
  14. string name = 2;
  15. int32 age = 3;
  16. // 字段修饰符
  17. // repeated 表示可变数组,类似切片类型
  18. repeated string hobby = 4;
  19. }
  20. // 定义开放调用的服务(接口)
  21. service UserInfoService{
  22. // 接口内的方法
  23. // 定义请求参数为UserRequest,响应参数为UserResponse
  24. rpc GetUserInfo (UserRequest) returns (UserResponse){}
  25. }
  26. // 写完后右键打开terminal,执行 protoc -I . --go_out=plugins=grpc:. ./user.proto
  27. // 执行完就可以在改目录下看到生成的user.pb.go文件,可以点进去看看

执行上面

  1. protoc -I . --go_out=plugins=grpc:. ./user.proto

生成user.pb.go

  1. // Code generated by protoc-gen-go. DO NOT EDIT.
  2. // source: user.proto
  3. package proto
  4. import (
  5. context "context"
  6. fmt "fmt"
  7. proto "github.com/golang/protobuf/proto"
  8. grpc "google.golang.org/grpc"
  9. codes "google.golang.org/grpc/codes"
  10. status "google.golang.org/grpc/status"
  11. math "math"
  12. )
  13. // Reference imports to suppress errors if they are not otherwise used.
  14. var _ = proto.Marshal
  15. var _ = fmt.Errorf
  16. var _ = math.Inf
  17. // This is a compile-time assertion to ensure that this generated file
  18. // is compatible with the proto package it is being compiled against.
  19. // A compilation error at this line likely means your copy of the
  20. // proto package needs to be updated.
  21. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
  22. type UserRequest struct {
  23. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
  24. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  25. XXX_unrecognized []byte `json:"-"`
  26. XXX_sizecache int32 `json:"-"`
  27. }
  28. func (m *UserRequest) Reset() { *m = UserRequest{} }
  29. func (m *UserRequest) String() string { return proto.CompactTextString(m) }
  30. func (*UserRequest) ProtoMessage() {}
  31. func (*UserRequest) Descriptor() ([]byte, []int) {
  32. return fileDescriptor_116e343673f7ffaf, []int{0}
  33. }
  34. func (m *UserRequest) XXX_Unmarshal(b []byte) error {
  35. return xxx_messageInfo_UserRequest.Unmarshal(m, b)
  36. }
  37. func (m *UserRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
  38. return xxx_messageInfo_UserRequest.Marshal(b, m, deterministic)
  39. }
  40. func (m *UserRequest) XXX_Merge(src proto.Message) {
  41. xxx_messageInfo_UserRequest.Merge(m, src)
  42. }
  43. func (m *UserRequest) XXX_Size() int {
  44. return xxx_messageInfo_UserRequest.Size(m)
  45. }
  46. func (m *UserRequest) XXX_DiscardUnknown() {
  47. xxx_messageInfo_UserRequest.DiscardUnknown(m)
  48. }
  49. var xxx_messageInfo_UserRequest proto.InternalMessageInfo
  50. func (m *UserRequest) GetName() string {
  51. if m != nil {
  52. return m.Name
  53. }
  54. return ""
  55. }
  56. type UserResponse struct {
  57. Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
  58. Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
  59. Age int32 `protobuf:"varint,3,opt,name=age,proto3" json:"age,omitempty"`
  60. Hobby []string `protobuf:"bytes,4,rep,name=hobby,proto3" json:"hobby,omitempty"`
  61. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  62. XXX_unrecognized []byte `json:"-"`
  63. XXX_sizecache int32 `json:"-"`
  64. }
  65. func (m *UserResponse) Reset() { *m = UserResponse{} }
  66. func (m *UserResponse) String() string { return proto.CompactTextString(m) }
  67. func (*UserResponse) ProtoMessage() {}
  68. func (*UserResponse) Descriptor() ([]byte, []int) {
  69. return fileDescriptor_116e343673f7ffaf, []int{1}
  70. }
  71. func (m *UserResponse) XXX_Unmarshal(b []byte) error {
  72. return xxx_messageInfo_UserResponse.Unmarshal(m, b)
  73. }
  74. func (m *UserResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
  75. return xxx_messageInfo_UserResponse.Marshal(b, m, deterministic)
  76. }
  77. func (m *UserResponse) XXX_Merge(src proto.Message) {
  78. xxx_messageInfo_UserResponse.Merge(m, src)
  79. }
  80. func (m *UserResponse) XXX_Size() int {
  81. return xxx_messageInfo_UserResponse.Size(m)
  82. }
  83. func (m *UserResponse) XXX_DiscardUnknown() {
  84. xxx_messageInfo_UserResponse.DiscardUnknown(m)
  85. }
  86. var xxx_messageInfo_UserResponse proto.InternalMessageInfo
  87. func (m *UserResponse) GetId() int32 {
  88. if m != nil {
  89. return m.Id
  90. }
  91. return 0
  92. }
  93. func (m *UserResponse) GetName() string {
  94. if m != nil {
  95. return m.Name
  96. }
  97. return ""
  98. }
  99. func (m *UserResponse) GetAge() int32 {
  100. if m != nil {
  101. return m.Age
  102. }
  103. return 0
  104. }
  105. func (m *UserResponse) GetHobby() []string {
  106. if m != nil {
  107. return m.Hobby
  108. }
  109. return nil
  110. }
  111. func init() {
  112. proto.RegisterType((*UserRequest)(nil), "proto.UserRequest")
  113. proto.RegisterType((*UserResponse)(nil), "proto.UserResponse")
  114. }
  115. func init() { proto.RegisterFile("user.proto", fileDescriptor_116e343673f7ffaf) }
  116. var fileDescriptor_116e343673f7ffaf = []byte{
  117. // 179 bytes of a gzipped FileDescriptorProto
  118. 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x2d, 0x4e, 0x2d,
  119. 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0x8a, 0x5c, 0xdc, 0xa1, 0xc5,
  120. 0xa9, 0x45, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42, 0x5c, 0x2c, 0x79, 0x89, 0xb9,
  121. 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x52, 0x14, 0x17, 0x0f, 0x44, 0x49,
  122. 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x10, 0x1f, 0x17, 0x53, 0x66, 0x0a, 0x58, 0x05, 0x6b, 0x10,
  123. 0x53, 0x66, 0x0a, 0x5c, 0x0f, 0x13, 0x42, 0x8f, 0x90, 0x00, 0x17, 0x73, 0x62, 0x7a, 0xaa, 0x04,
  124. 0x33, 0x58, 0x11, 0x88, 0x29, 0x24, 0xc2, 0xc5, 0x9a, 0x91, 0x9f, 0x94, 0x54, 0x29, 0xc1, 0xa2,
  125. 0xc0, 0xac, 0xc1, 0x19, 0x04, 0xe1, 0x18, 0x79, 0x73, 0xf1, 0x83, 0xcc, 0xf6, 0xcc, 0x4b, 0xcb,
  126. 0x0f, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0x15, 0xb2, 0xe0, 0xe2, 0x76, 0x4f, 0x2d, 0x81, 0x89,
  127. 0x0a, 0x09, 0x41, 0xdc, 0xab, 0x87, 0xe4, 0x4a, 0x29, 0x61, 0x14, 0x31, 0x88, 0xb3, 0x94, 0x18,
  128. 0x92, 0xd8, 0xc0, 0xa2, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x69, 0xe5, 0x60, 0x7a, 0xe7,
  129. 0x00, 0x00, 0x00,
  130. }
  131. // Reference imports to suppress errors if they are not otherwise used.
  132. var _ context.Context
  133. var _ grpc.ClientConn
  134. // This is a compile-time assertion to ensure that this generated file
  135. // is compatible with the grpc package it is being compiled against.
  136. const _ = grpc.SupportPackageIsVersion4
  137. // UserInfoServiceClient is the client API for UserInfoService service.
  138. //
  139. // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
  140. type UserInfoServiceClient interface {
  141. GetUserInfo(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error)
  142. }
  143. type userInfoServiceClient struct {
  144. cc *grpc.ClientConn
  145. }
  146. func NewUserInfoServiceClient(cc *grpc.ClientConn) UserInfoServiceClient {
  147. return &userInfoServiceClient{cc}
  148. }
  149. func (c *userInfoServiceClient) GetUserInfo(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error) {
  150. out := new(UserResponse)
  151. err := c.cc.Invoke(ctx, "/proto.UserInfoService/GetUserInfo", in, out, opts...)
  152. if err != nil {
  153. return nil, err
  154. }
  155. return out, nil
  156. }
  157. // UserInfoServiceServer is the server API for UserInfoService service.
  158. type UserInfoServiceServer interface {
  159. GetUserInfo(context.Context, *UserRequest) (*UserResponse, error)
  160. }
  161. // UnimplementedUserInfoServiceServer can be embedded to have forward compatible implementations.
  162. type UnimplementedUserInfoServiceServer struct {
  163. }
  164. func (*UnimplementedUserInfoServiceServer) GetUserInfo(ctx context.Context, req *UserRequest) (*UserResponse, error) {
  165. return nil, status.Errorf(codes.Unimplemented, "method GetUserInfo not implemented")
  166. }
  167. func RegisterUserInfoServiceServer(s *grpc.Server, srv UserInfoServiceServer) {
  168. s.RegisterService(&_UserInfoService_serviceDesc, srv)
  169. }
  170. func _UserInfoService_GetUserInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
  171. in := new(UserRequest)
  172. if err := dec(in); err != nil {
  173. return nil, err
  174. }
  175. if interceptor == nil {
  176. return srv.(UserInfoServiceServer).GetUserInfo(ctx, in)
  177. }
  178. info := &grpc.UnaryServerInfo{
  179. Server: srv,
  180. FullMethod: "/proto.UserInfoService/GetUserInfo",
  181. }
  182. handler := func(ctx context.Context, req interface{}) (interface{}, error) {
  183. return srv.(UserInfoServiceServer).GetUserInfo(ctx, req.(*UserRequest))
  184. }
  185. return interceptor(ctx, in, info, handler)
  186. }
  187. var _UserInfoService_serviceDesc = grpc.ServiceDesc{
  188. ServiceName: "proto.UserInfoService",
  189. HandlerType: (*UserInfoServiceServer)(nil),
  190. Methods: []grpc.MethodDesc{
  191. {
  192. MethodName: "GetUserInfo",
  193. Handler: _UserInfoService_GetUserInfo_Handler,
  194. },
  195. },
  196. Streams: []grpc.StreamDesc{},
  197. Metadata: "user.proto",
  198. }

创建server

创建server服务 user_server.go

package main

import (
    "context"
    "fmt"
    "net"

    pb "github.com/baxiang/go-grpc/proto"
    "google.golang.org/grpc"
)

type UserInfoService struct{}

func (user *UserInfoService) GetUserInfo(ctx context.Context, req *pb.UserRequest) (resp *pb.UserResponse, err error) {
    if req.Name == "foo" {
        resp = &pb.UserResponse{
            Id:    1,
            Name:  req.Name,
            Age:   18,
            Hobby: []string{"coding"},
        }
    }
    return
}

func main() {
    listener, err := net.Listen("tcp", "127.0.0.1:8080")
    if err != nil {
        fmt.Printf("listen error:%s\n", err)
    }
    server := grpc.NewServer()
    pb.RegisterUserInfoServiceServer(server, &UserInfoService{})
    err = server.Serve(listener)
    if err != nil {
        fmt.Printf("serve error:%s\n", err)
    }
}

启动server

go run user_server.go

创建client

创建user_client

package main

import (
    "context"
    "fmt"
    pb "github.com/baxiang/go-grpc/proto"
    "google.golang.org/grpc"
)

func main() {
    conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
    if err != nil {
        fmt.Printf("connect error: %s\n", err)
    }
    //
    defer conn.Close()
    client := pb.NewUserInfoServiceClient(conn)
    req := &pb.UserRequest{Name: "foo"}
    resp, err := client.GetUserInfo(context.Background(), req)
    if err != nil {
        fmt.Printf("resp error: %s\n", err)
    }
    fmt.Printf("resp result: %v\n", resp)
}

执行结果

resp result: id:1 name:"foo" age:18 hobby:"coding"

gRPC 入门2

创建名称是operation.proto

syntax = "proto3";
package pb;

// 算术运算请求结构
message operaRequest {
    int32 a = 1;
    int32 b = 2;
}

message operaResponse {
    int32 result = 1;  //结果

}

// rpc方法
service operaService {
    rpc add (operaRequest) returns (operaResponse);    // 加法运算
    rpc sub (operaRequest) returns (operaResponse);      //减法运算
}

生成go 相关的代码

protoc --go_out=plugins=grpc:{go代码输出路径} {proto文件}
 protoc --go_out=plugins=grpc:. operation.proto

server

package main

import (
    "context"
    "fmt"
    pb "github.com/baxiang/go-note/grpc/proto"
    "google.golang.org/grpc"
    //"google.golang.org/grpc/reflection"
    "net"
)

type OperaService struct {
}

// 业务逻辑代码
func (a *OperaService) Add(ctx context.Context, req *pb.OperaRequest) (res *pb.OperaResponse, err error) {
    res = &pb.OperaResponse{
        Result: req.A + req.B,
    }
    return
}

func (a *OperaService) Sub(ctx context.Context, req *pb.OperaRequest) (res *pb.OperaResponse, err error) {
    res = &pb.OperaResponse{
        Result: req.A - req.B,
    }
    return
}
func main() {
    //1.监听
    list, err := net.Listen("tcp", ":8082")
    if err != nil {
        fmt.Printf("list err:%v \n", err)
        return
    }
    //2、实例化gRPC
    s := grpc.NewServer()
    //3.在gRPC上注册微服务
    //第二个参数接口类型的变量"errors"
    pb.RegisterOperaServiceServer(s, &OperaService{})

    //reflection.Register(s)////关闭注册反射
    //4.启动gRPC服务端
    s.Serve(list)
}

客户端代码

package main

import (
    pb "github.com/baxiang/go-note/grpc/proto"
    "google.golang.org/grpc"
    "log"
    "context"
    "fmt"
)

func main() {
    //1.创建与gRPC服务端的连接 grpc.WithInsecure() 建立一个安全连接(跳过了对服务器证书的验证)
    conn, err := grpc.Dial("127.0.0.1:8082", grpc.WithInsecure())
    defer conn.Close()
    if err != nil {
        log.Fatalf("conn err %s\n", err)
        return
    }
    //2.实例化gRPC客户端
    client := pb.NewOperaServiceClient(conn)
    //3.组装参数
    req := pb.OperaRequest{A: 5, B: 2}
    //4.调用接口
    resp, err := client.Add(context.Background(), &req)
    if err != nil {
        log.Fatalf("add error: %s\n", err)
        return
    }
    fmt.Printf("%d + %d = %d\n", req.GetA(), req.GetB(), resp.GetResult())

    resp, err = client.Sub(context.Background(), &req)
    if err != nil {
        log.Fatalf("sub error %s\n", err)
    }
    fmt.Printf("%d - %d = %d\n", req.A, req.B, resp.GetResult())
}

gRPC模式

gRPC主要有4种请求/响应模式,分别是:
(1) 简单模式(Simple RPC)
这种模式最为传统,即客户端发起一次请求,服务端响应一个数据,这和大家平时熟悉的RPC没有什么大的区别,所以不再详细介绍。
(2) 服务端数据流模式(Server-side streaming RPC)
这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。
(3) 客户端数据流模式(Client-side streaming RPC)
与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。典型的例子是物联网终端向服务器报送数据。
(4) 双向数据流模式(Bidirectional streaming RPC)
顾名思义,这是客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是可以实现实时交互。典型的例子是聊天机器人。

gRPC流

syntax = "proto3";
package pb;

message ChatReq {
    string input = 1;
}
message ChatRes {
    string output = 1;
}

//方法
service Chat {
    //关键字stream指定启用流特性 stream 支持双向流 Send(),Recv()
    rpc Hello (stream ChatReq) returns (stream ChatRes);
}

服务器端

package main

import (
    pb "github.com/baxiang/go-note/grpc/proto"
    "google.golang.org/grpc"
    "io"
    "log"
    "net"
    "strconv"
)

//GRPC流 服务端实现流服务


type ChatService struct {
}

func (p *ChatService) Hello(stream  pb.Chat_HelloServer)  error {
    ctx := stream.Context()
    for {
        select {
        case <-ctx.Done():
            log.Println("收到客户端通过context发出的终止信号")
            return ctx.Err()
        default:
            // 接收从客户端发来的消息
            rec, err := stream.Recv()
            if err == io.EOF {
                log.Println("客户端发送的数据流结束")
                return nil
            }
            if err != nil {
                log.Println("接收数据出错:", err)
                return err
            }

            // 如果接收正常,则根据接收到的 字符串 执行相应的指令
            switch rec.Input {
            case "finish\n":
                log.Println("收到'结束对话'指令")
                if err := stream.Send(&pb.ChatRes{Output: "收到结束指令"}); err != nil {
                    return err
                }
                // 收到结束指令时,通过 return nil 终止双向数据流
                return nil

            case "back\n":
                log.Println("收到'返回数据流'指令")
                // 收到 收到'返回数据流'指令, 连续返回 10 条数据
                for i := 0; i < 5; i++ {
                    if err := stream.Send(&pb.ChatRes{Output: "数据流 #" + strconv.Itoa(i)}); err != nil {
                        return err
                    }
                }

            default:
                // 缺省情况下, 返回 '服务端返回: ' + 输入信息
                log.Printf("[收到客户端消息]: %s", rec.Input)
                if err := stream.Send(&pb.ChatRes{Output: "服务端返回: " + rec.Input}); err != nil {
                    return err
                }
            }
        }
    }
}



//注册rpc服务
func main() {
    server := grpc.NewServer()
    pb.RegisterChatServer(server, &ChatService{})
    address, err := net.Listen("tcp", ":8083")
    if err != nil {
        log.Printf("Listen err:%s \n", err)
        return
    }
    server.Serve(address)
}

客户端

package main

import (
    "bufio"
    pb "github.com/baxiang/go-note/grpc/proto"
    "google.golang.org/grpc"
    "io"
    "os"
    "log"
    "context"
)

func main() {
    // 创建连接
    conn, err := grpc.Dial("127.0.0.1:8083", grpc.WithInsecure())
    if err != nil {
        log.Printf("conn error %v\n", err)
        return
    }
    defer conn.Close()

    // 声明客户端
    client := pb.NewChatClient(conn)
    // 创建双向数据流
    stream, err := client.Hello(context.Background())
    if err != nil {
        log.Printf("创建数据流失败: [%v]\n", err)
    }

    // 启动一个 goroutine 接收命令行输入的指令
    go func() {
        log.Println("请输入消息...")
        buf := bufio.NewReader(os.Stdin)
        for {
            // 获取 命令行输入的字符串, 以回车 \n 作为结束标志
            s, _ := buf.ReadString('\n')
            // 向服务端发送 指令
            if err := stream.Send(&pb.ChatReq{Input: s}); err != nil {
                return
            }
        }
    }()

    for {
        // 接收从 服务端返回的数据流
        rec, err := stream.Recv()
        if err == io.EOF {
            log.Println("收到服务端的结束信号")
            break   //如果收到结束信号,则退出“接收循环”,结束客户端程序
        }

        if err != nil {
            // TODO: 处理接收错误
            log.Println("接收数据出错:", err)
            break
        }
        // 没有错误的情况下,打印来自服务端的消息
        log.Printf("[客户端收到]: %s", rec.Output)
    }
}

参考

https://www.grpc.io/docs/languages/go/quickstart/
https://developers.google.com/protocol-buffers/docs/reference/go-generated
https://www.grpc.io/docs/tutorials/basic/go/#why-use-grpc
https://github.com/grpc-ecosystem/go-grpc-middleware
https://www.bookstack.cn/read/go-grpc/summary.md
https://www.jianshu.com/p/5158d6686769
http://doc.oschina.net/grpc?t=60133
https://segmentfault.com/a/1190000016503114
https://github.com/18301662572/mylearn/tree/85ca108272887fc2d5f6fabbb1c7d97da20037dc/25rpc
https://segmentfault.com/a/1190000007880647
https://github.com/jergoo/go-grpc-practice-guide
http://www.dockone.io/article/10028
https://github.com/Bingjian-Zhu/go-grpc-example
https://github.com/jergoo/go-grpc-tutorial