# RPC gRPC Twirp ProtoBuf之间的关系

简单介绍这几个项目的关系:

- ProtoBuf是一种序列化数据结构的协议
- protoc是一个能把proto数据结构转换为各种语言代码的工具
- RPC是一种通信协议
- gRPC是一种使用ProtoBuf作为接口描述语言的一个RPC实现方案

# RPC

在分布式计算,远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像**调用本地程序一样**,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种**服务器-客户端(Client/Server)模式**,经典实现是一个通过发送请求-接受回应进行信息交互的系统。
RPC是一种进程间通信的模式,程序分布在不同的地址空间里。如果在同一主机里,RPC可以通过不同的虚拟地址空间(即便使用相同的物理地址)进行通讯,而在不同的主机间,则通过不同的物理地址进行交互。许多技术(常常是不兼容)都是基于这种概念而实现的。



# ProtoBuf
Protocol Buffers(简称ProtoBuf)是一种序列化数据结构的协议。对于透过管道(pipeline)或存储资料进行通信的程序开发上是很有用的。这个方法包含一个接口描述语言,描述一些数据结构,并提供程序工具根据这些>描述产生代码,用于将这些数据结构产生或解析资料流。[官方文档-Language Guide (proto3)](https://protobuf.dev/overview/)



# Protoc([Protocol Compiler](https://github.com/protocolbuffers/protobuf)编译器)
要生成Java、Python、C ++、Go、Ruby、Objective-C或C#代码,您需要使用.proto文件中定义的消息类型,需要在.proto上运行协议缓冲区编译器协议。如果尚未安装编译器,请下载软件包并按照自述文件中的说明进行操作。对于Go,还需要为编译器安装一个特殊的代码生成器插件,请阅读Go Generated Code。
## protoc-gen-go([生成go代码插件](https://github.com/protocolbuffers/protobuf-go))
protocol buffer编译器需要一个插件来生成Go代码


# GRPC
gRPC(gRPC Remote Procedure Calls)是Google发起的一个开源远程过程调用(Remote procedure call)系统。该系统基于HTTP/2协议传输,使用Protocol Buffers作为接口描述语言。***实现RPC协议的框架***。
其他功能:
- 认证(authentication)
- 双向流(bidirectional streaming)
- 流控制(flow control)
- 超时(timeouts)

可能的使用场景:
- 内部微服务之间的通信。
- 高数据负载(gRPC 使用协议缓冲区,其速度最高可比 REST 调用快七倍)。
- 您只需要一个简单的服务定义,不需要编写完整的客户端库。
- 在gRPC服务器中使用流式传输gRPC来构建响应更快的应用和 API。

# Twirp[是一个基于 Google Protobuf 的 RPC 框架](https://twitchtv.github.io/twirp/docs/intro.html)
Twirp通过在.proto文件中定义服务,然后自动生产服务器和客户端的代码。让我们可以将更多的精力放在业务逻辑上。咦?这不就是 gRPC 吗?不同的是,gRPC 自己实现了一套 HTTP 服务器和网络传输层,twirp 使用标准库net/http。另外 gRPC 只支持 HTTP/2 协议,twirp 还可以运行在 HTTP 1.1 之上。同时 twirp 还可以使用 JSON 格式交互。当然并不是说 twirp 比 gRPC 好,只是多了解一种框架也就多了一个选择。





# kratos框架项目结构:
```bash
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── api
│   └── helloworld
│       ├── helloworld.pb.go   // protobuf生成的go的代码
│       ├── helloworld.proto   // proto协议文件原始定义
│       ├── helloworld_grpc.pb.go    // --go_out=plugins=grpc:service go插件生成的grpc协议文件
│       └── v1
│           ├── error_reason.pb.go
│           ├── error_reason.proto
│           ├── greeter.pb.go
│           ├── greeter.proto
│           ├── greeter_grpc.pb.go    // grpc客户端/服务端rpc调用
│           └── greeter_http.pb.go    // restful api net/http 调用
├── cmd
│   └── helloworld
│       ├── main.go
│       ├── wire.go
│       └── wire_gen.go
├── configs
│   └── config.yaml
├── go.mod
├── go.sum
├── internal
│   ├── biz
│   │   ├── README.md
│   │   ├── biz.go
│   │   └── greeter.go
│   ├── conf
│   │   ├── conf.pb.go
│   │   └── conf.proto
│   ├── data
│   │   ├── README.md
│   │   ├── data.go
│   │   └── greeter.go
│   ├── server
│   │   ├── grpc.go
│   │   ├── http.go
│   │   └── server.go
│   └── service
│       ├── README.md
│       ├── greeter.go
│       ├── helloworld.go
│       └── service.go
├── openapi.yaml
└── third_party
   ├── README.md
   ├── errors
   │   └── errors.proto
   ├── google
   │   ├── api
   │   └── v3
   │       ├── annotations.proto
   │       └── openapi.proto
   └── validate
       ├── README.md
       └── validate.proto

```
kratos框架我们了解到定义原始的proto文件、会帮我们生成**符合服务间调用的grpc(client/server)源代码、以及对外暴露的restful api既符合protobuf协议又符合json格式的数据、一举两得,可以让我们专注于业务逻辑的处理***。

## Kratos 一套轻量级 Go 微服务框架,包含大量微服务相关功能及工具
> 名字来源于:《战神》游戏以希腊神话为背景,讲述奎托斯(Kratos)由凡人成为战神并展开弑神屠杀的冒险经历。

Principles
- 简单:不过度设计,代码平实简单;
- 通用:通用业务开发所需要的基础库的功能;
- 高效:提高业务迭代的效率;
- 稳定:基础库可测试性高,覆盖率高,有线上实践安全可靠;
- 健壮:通过良好的基础库设计,减少错用;
- 高性能:性能高,但不特定为了性能做 hack 优化,引入 unsafe ;
- 扩展性:良好的接口设计,来扩展实现,或者通过新增基础库目录来扩展功能;
- 容错性:为失败设计,大量引入对 SRE 的理解,鲁棒性高;
- 工具链:包含大量工具链,比如 cache 代码生成,lint 工具等等

## Features

- APIs :协议通信以 HTTP/gRPC 为基础,通过 Protobuf 进行定义;
- Errors :通过 Protobuf 的 Enum 作为错误码定义,以及工具生成判定接口;
- Metadata :在协议通信 HTTP/gRPC 中,通过 Middleware 规范化服务元信息传递;
- Config :支持多数据源方式,进行配置合并铺平,通过 Atomic 方式支持动态配置;
- Logger :标准日志接口,可方便集成三方 log 库,并可通过 fluentd 收集日志;
- Metrics :统一指标接口,可以实现各种指标系统,默认集成 Prometheus;
- Tracing :遵循 OpenTelemetry 规范定义,以实现微服务链路追踪;
- Encoding :支持 Accept 和 Content-Type 进行自动选择内容编码;
- Transport :通用的 HTTP /gRPC 传输层,实现统一的 Middleware 插件支持;
- Registry :实现统一注册中心接口,可插件化对接各种注册中心;
- Validation: 通过Protobuf统一定义校验规则,并同时适用于HTTP/gRPC服务.
- SwaggerAPI: 通过集成第三方Swagger插件 能够自动生成Swagger API json并启动一个内置的Swagger UI服务.

## kratos 强大的grpc和http兼容能力、服务端注册源码
### http server
```go
package server

import (
  v1 "helloworld/api/helloworld/v1"
  "helloworld/internal/conf"
  "helloworld/internal/service"

  "github.com/go-kratos/kratos/v2/log"
  "github.com/go-kratos/kratos/v2/middleware/recovery"
  "github.com/go-kratos/kratos/v2/transport/http"
)

// NewHTTPServer new an HTTP server.
func NewHTTPServer(c *conf.Server, greeter *service.GreeterService, logger log.Logger) *http.Server {
  var opts = []http.ServerOption{
     http.Middleware(
        recovery.Recovery(),
     ),
  }
  if c.Http.Network != "" {
     opts = append(opts, http.Network(c.Http.Network))
  }
  if c.Http.Addr != "" {
     opts = append(opts, http.Address(c.Http.Addr))
  }
  if c.Http.Timeout != nil {
     opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
  }
  srv := http.NewServer(opts...)
  v1.RegisterGreeterHTTPServer(srv, greeter)
  return srv
}

```

### grpc server

```go
package server

import (
  v1 "helloworld/api/helloworld/v1"
  "helloworld/internal/conf"
  "helloworld/internal/service"

  "github.com/go-kratos/kratos/v2/log"
  "github.com/go-kratos/kratos/v2/middleware/recovery"
  "github.com/go-kratos/kratos/v2/transport/grpc"
)

// NewGRPCServer new a gRPC server.
func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, logger log.Logger) *grpc.Server {
  var opts = []grpc.ServerOption{
     grpc.Middleware(
        recovery.Recovery(),
     ),
  }
  if c.Grpc.Network != "" {
     opts = append(opts, grpc.Network(c.Grpc.Network))
  }
  if c.Grpc.Addr != "" {
     opts = append(opts, grpc.Address(c.Grpc.Addr))
  }
  if c.Grpc.Timeout != nil {
     opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration()))
  }
  srv := grpc.NewServer(opts...)
  v1.RegisterGreeterServer(srv, greeter)
  return srv
}

```

以上kratos框架的server端可以同时监听grpc server和http server

### protoc-gen-go-grpc
```go
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc             v3.19.3
// source: helloworld/v1/greeter.proto

package v1

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

// 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.
type GreeterClient interface {
  // Sends a greeting
  SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, 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) (*HelloReply, error) {
  out := new(HelloReply)
  err := c.cc.Invoke(ctx, "/helloworld.v1.Greeter/SayHello", in, out, opts...)
  if err != nil {
     return nil, err
  }
  return out, nil
}

// GreeterServer is the server API for Greeter service.
// All implementations must embed UnimplementedGreeterServer
// for forward compatibility
type GreeterServer interface {
  // Sends a greeting
  SayHello(context.Context, *HelloRequest) (*HelloReply, error)
  mustEmbedUnimplementedGreeterServer()
}

// UnimplementedGreeterServer must be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}

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

// 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) {
  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: "/helloworld.v1.Greeter/SayHello",
  }
  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: "helloworld.v1.Greeter",
  HandlerType: (*GreeterServer)(nil),
  Methods: []grpc.MethodDesc{
     {
        MethodName: "SayHello",
        Handler:    _Greeter_SayHello_Handler,
     },
  },
  Streams:  []grpc.StreamDesc{},
  Metadata: "helloworld/v1/greeter.proto",
}

```


### protoc-gen-go-http

```go
// Code generated by protoc-gen-go-http. DO NOT EDIT.
// versions:
// protoc-gen-go-http v2.1.3

package v1

import (
  context "context"
  http "github.com/go-kratos/kratos/v2/transport/http"
  binding "github.com/go-kratos/kratos/v2/transport/http/binding"
)

// This is a compile-time assertion to ensure that this generated file
// is compatible with the kratos package it is being compiled against.
var _ = new(context.Context)
var _ = binding.EncodeURL

const _ = http.SupportPackageIsVersion1

type GreeterHTTPServer interface {
  SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}

func RegisterGreeterHTTPServer(s *http.Server, srv GreeterHTTPServer) {
  r := s.Route("/")
  r.GET("/helloworld/{name}", _Greeter_SayHello0_HTTP_Handler(srv))
}

func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Context) error {
  return func(ctx http.Context) error {
     var in HelloRequest
     if err := ctx.BindQuery(&in); err != nil {
        return err
     }
     if err := ctx.BindVars(&in); err != nil {
        return err
     }
     http.SetOperation(ctx, "/helloworld.v1.Greeter/SayHello")
     h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.SayHello(ctx, req.(*HelloRequest))
     })
     out, err := h(ctx, &in)
     if err != nil {
        return err
     }
     reply := out.(*HelloReply)
     return ctx.Result(200, reply)
  }
}

type GreeterHTTPClient interface {
  SayHello(ctx context.Context, req *HelloRequest, opts ...http.CallOption) (rsp *HelloReply, err error)
}

type GreeterHTTPClientImpl struct {
  cc *http.Client
}

func NewGreeterHTTPClient(client *http.Client) GreeterHTTPClient {
  return &GreeterHTTPClientImpl{client}
}

func (c *GreeterHTTPClientImpl) SayHello(ctx context.Context, in *HelloRequest, opts ...http.CallOption) (*HelloReply, error) {
  var out HelloReply
  pattern := "/helloworld/{name}"
  path := binding.EncodeURL(pattern, in, true)
  opts = append(opts, http.Operation("/helloworld.v1.Greeter/SayHello"))
  opts = append(opts, http.PathTemplate(pattern))
  err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...)
  if err != nil {
     return nil, err
  }
  return &out, err
}

```

# Twirp 框架demo项目结构
```bash
~/go/src/twirp-helloworld/example                                                                                                                                                                     19:59:52
❯                                19:59:52
│       └── statter.go
├── gen.go
├── service.pb.go
│   ├── cmd
│   │   ├── client
│   │   │   └── main.go
│   │   └── server
│   │       ├── README.md
│   │       ├── main.go
│   │       └── statter.go
│   ├── gen.go
│   ├── service.pb.go
│   ├── service.proto
│   └── service.twirp.go   // go插件twirp生成协议文件 --twirp_out=. rpc/haberdasher/service.proto
├── go.mod
└── go.sum
```

## Twirp框架rpc实现
```go
// Copyright 2018 Twitch Interactive, Inc.  All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
// use this file except in compliance with the License. A copy of the License is
// located at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// or in the "license" file accompanying this file. This file is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package main

import (
  "context"
  "log"
  "math/rand"
  "net/http"
  "os"

  "github.com/twitchtv/twirp"
  "github.com/twitchtv/twirp/example"
  "github.com/twitchtv/twirp/hooks/statsd"
)

type randomHaberdasher struct{}

func (h *randomHaberdasher) MakeHat(ctx context.Context, size *example.Size) (*example.Hat, error) {
  if size.Inches <= 0 {
     return nil, twirp.InvalidArgumentError("Inches", "I can't make a hat that small!")
  }
  colors := []string{"white", "black", "brown", "red", "blue"}
  names := []string{"bowler", "baseball cap", "top hat", "derby"}
  return &example.Hat{
     Size:  size.Inches,
     Color: colors[rand.Intn(len(colors))],
     Name:  names[rand.Intn(len(names))],
  }, nil
}

func main() {
  hook := statsd.NewStatsdServerHooks(LoggingStatter{os.Stderr})
  server := example.NewHaberdasherServer(&randomHaberdasher{}, hook)
  log.Fatal(http.ListenAndServe(":8080", server))
}

```

### RestFulApi
```shell
curl --request "POST" \
   --header "Content-Type: application/json" \
   --data '{"inches": 0}' \
   http://127.0.0.1:8080/twirp/twitch.twirp.example.Haberdasher/MakeHat
{"code":"invalid_argument","msg":"Inches I can't make a hat that small!","meta":{"argument":"Inches"}}%
```

### protobuf 格式
```go
// Copyright 2018 Twitch Interactive, Inc.  All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
// use this file except in compliance with the License. A copy of the License is
// located at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// or in the "license" file accompanying this file. This file is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package main

import (
  "context"
  "fmt"
  "log"
  "net/http"

  "github.com/twitchtv/twirp"
  "github.com/twitchtv/twirp/example"
)

func main() {
  client := example.NewHaberdasherJSONClient("http://localhost:8080", &http.Client{})
             //proto:=example.NewHaberdasherProtobufClient("http://localhost:8080", &http.Client{})
  var (
     hat *example.Hat
     err error
  )
  for i := 0; i < 5; i++ {
     hat, err = client.MakeHat(context.Background(), &example.Size{Inches: 12})
     if err != nil {
        if twerr, ok := err.(twirp.Error); ok {
           if twerr.Meta("retryable") != "" {
              // Log the error and go again.
              log.Printf("got error %q, retrying", twerr)
              continue
           }
        }
        // This was some fatal error!
        log.Fatal(err)
     }
  }
  fmt.Printf("%+v", hat)
}
```