Ricardo的博客

我本逍遥一散人

gin 框架实现 GRPC 的服务端和客户端

前提准备

初始化一个Go Module

mkdir grpc_calculator && cd grpc_calculator
go mod init grpc_calculator

安装 gRPC 和 protobuf 相关依赖

go get -u google.golang.org/grpc
go get -u google.golang.org/protobuf/cmd/protoc-gen-go
go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc

安装完后请检查你配置的GOPATH/bin目录下是否存在 protoc-gen-go-grpc 可执行文件,若是没有,在bin目录直接执行

go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest

编写 protobuf 文件定义服务和消息

在项目根目录下创建文件 calculator.proto 并且输入

syntax = "proto3";

package calculator;

import "google/api/annotations.proto";

option go_package = "github.com/dyjh/grpc_calculator/calculator";

service Calculator {
  rpc Calculate(CalculateRequest) returns (CalculateResponse) {
    option (google.api.http) = {
      post: "/calculate"
      body: "*"
    };
  }
  rpc Compare(CompareRequest) returns (CompareResponse) {
    option (google.api.http) = {
      post: "/compare"
      body: "*"
    };
  }
}

message CalculateRequest {
  float num1 = 1;
  float num2 = 2;
  Operation operation = 3;
}

message CalculateResponse {
  float result = 1;
}

message CompareRequest {
  float num1 = 1;
  float num2 = 2;
}

message CompareResponse {
  float max = 1;
}

enum Operation {
  ADD = 0;
  SUBTRACT = 1;
  MULTIPLY = 2;
  DIVIDE = 3;
}

在这里,我们为 Calculate 和 Compare 方法添加了 google.api.http 注释,分别指定了 post: "/calculate" 和 post: "/compare"。在尝试使用 grpc-gateway 时,HTTP 方法(例如 "post")才能正确地与 gRPC 方法关联。

当然,增加了google.api注释,我们需要在项目内手动下载相关的文件 google/api/annotations.proto ,及其依赖文件,下载后存放目录为

wei xin jie tu 20230409134627 - gin 框架实现 GRPC 的服务端和客户端

生成 Go 代码

在项目根目录下创建文件夹 calculator, 然后运行以下命令生成 Go 代码:

protoc --go_out=./calculator --go_opt=paths=source_relative --go-grpc_out=./calculator --go-grpc_opt=paths=source_relative --grpc-gateway_out=./calculator --grpc-gateway_opt=paths=source_relative calculator.proto

实现 gRPC 服务端

创建一个名为 server.go 的文件,并添加以下内容:

package main

import (
    "context"
    "fmt"
    "log"
    "net"

    "github.com/dyjh/grpc_calculator/calculator"
    "google.golang.org/grpc"
)

type server struct {
    calculator.UnimplementedCalculatorServer
}

func (*server) Calculate(ctx context.Context, req *calculator.CalculateRequest) (*calculator.CalculateResponse, error) {
    num1 := req.GetNum1()
    num2 := req.GetNum2()
    operation := req.GetOperation()

    var result float32
    switch operation {
    case calculator.Operation_ADD:
        result = num1 + num2
    case calculator.Operation_SUBTRACT:
        result = num1 - num2
    case calculator.Operation_MULTIPLY:
        result = num1 * num2
    case calculator.Operation_DIVIDE:
        if num2 == 0 {
            return nil, fmt.Errorf("division by zero is not allowed")
        }
        result = num1 / num2
    }

    return &calculator.CalculateResponse{Result: result}, nil
}

func (*server) Compare(ctx context.Context, req *calculator.CompareRequest) (*calculator.CompareResponse, error) {
    num1 := req.GetNum1()
    num2 := req.GetNum2()

    max := num1
    if num2 > num1 {
        max = num2
    }

    return &calculator.CompareResponse{Max: max}, nil
}

func main() {
    lis, err := net.Listen("tcp", "0.0.0.0:50051")
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }

    s := grpc.NewServer()
    calculator.RegisterCalculatorServer(s, &server{})

    if err := s.Serve(lis); err != nil {
        log.Fatalf("Failed to serve: %v", err)
    }
}

实现 gRPC 客户端(HTTP)

创建一个名为 client.go 的文件,并添加以下内容:

package main

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

    "github.com/dyjh/grpc_calculator/calculator"
    "github.com/gin-gonic/gin"
    "google.golang.org/grpc"
)

func main() {
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("Failed to connect: %v", err)
    }
    defer conn.Close()

    c := calculator.NewCalculatorClient(conn)

    r := gin.Default()

    // 路由1:计算操作
    r.GET("/calculate/:operation/:num1/:num2", func(ctx *gin.Context) {
        num1, err := strconv.ParseFloat(ctx.Param("num1"), 32)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter num1"})
            return
        }

        num2, err := strconv.ParseFloat(ctx.Param("num2"), 32)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter num2"})
            return
        }

        op := ctx.Param("operation")
        var operation calculator.Operation
        switch op {
        case "add":
            operation = calculator.Operation_ADD
        case "subtract":
            operation = calculator.Operation_SUBTRACT
        case "multiply":
            operation = calculator.Operation_MULTIPLY
        case "divide":
            operation = calculator.Operation_DIVIDE
        default:
            ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid operation"})
            return
        }

        calculateReq := &calculator.CalculateRequest{
            Num1:      float32(num1),
            Num2:      float32(num2),
            Operation: operation,
        }

        calculateRes, err := c.Calculate(context.Background(), calculateReq)
        if err != nil {
            ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }

        ctx.JSON(http.StatusOK, gin.H{"result": calculateRes.GetResult()})
    })

    // 路由2:
    r.GET("/compare/:num1/:num2", func(ctx *gin.Context) {
        num1, err := strconv.ParseFloat(ctx.Param("num1"), 32)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter num1"})
            return
        }
        num2, err := strconv.ParseFloat(ctx.Param("num2"), 32)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter num2"})
            return
        }

        compareReq := &calculator.CompareRequest{
            Num1: float32(num1),
            Num2: float32(num2),
        }

        compareRes, err := c.Compare(context.Background(), compareReq)
        if err != nil {
            ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }

        ctx.JSON(http.StatusOK, gin.H{"max": compareRes.GetMax()})
    })

    r.Run(":8080")
}

现在,你可以运行 client.go,通过 HTTP 调用相应的路由。

例如,你可以使用以下 URL 访问计算操作的路由:

http://localhost:8080/calculate/add/10/5
http://localhost:8080/calculate/subtract/10/5
http://localhost:8080/calculate/multiply/10/5
http://localhost:8080/calculate/divide/10/5

项目地址

照旧文章末尾给大家附上demo地址:Github

Hyperf 实现简单的客服聊天系统

上一篇

Docker 部署 kong 并且实现 grpc 兼容

下一篇
评论
发表评论 说点什么
还没有评论
285
0