天天看点

ProtoBuf之在GoLang中实现GRPC服务GRPC是什么环境准备测试

GRPC是什么

GRPC是RPC(远程过程调用)实现的一种框架,可以使得两个服务调用对方的api仿佛是在调用本地方法一样,通过tcp访问实现。本文将通过ProtoBuf文件定义服务之间的访问接口,之后通过grpc和http成功访问(Golang实现)。

环境准备

1. 新建go mod项目

在GoLand IDE里按照如下图新建一个go mod项目

ProtoBuf之在GoLang中实现GRPC服务GRPC是什么环境准备测试

2. 准备ProtoBuf文件:HelloService.proto

syntax = "proto3";
package proto;
option go_package = "./proto";
import "google/api/annotations.proto";

message HelloServiceRequest {
    string name = 1;
    string age = 2;
}

message HelloServiceResponse {
    string result = 1;
}

service Service {
    rpc sayHello(HelloServiceRequest) returns (HelloServiceResponse) {
        option (google.api.http) = {
            get: "/say_hello"
        };
    }
}
           

3. 添加提供http访问所必要的proto依赖文件

在HelloService.proto文件所在目录下新建google/api/目录,往其中添加annotations.proto,field_behavior.proto,http.proto和httpbody.proto四个文件。这四个文件可以从google api github 中复制

4. 创建tool.go文件并使用go mod tidy命令下载依赖

tool.go

package tools

import (
	_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
	_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
	_ "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
	_ "google.golang.org/protobuf/cmd/protoc-gen-go"
)
           

在tool.go写入以上代码后,在项目home目录下执行以下命令下载依赖。

go mod tidy
           

5. 配置GOBIN路径并安装go grpc相关的可执行文件

export GOBIN=$GOPATH/bin
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 google.golang.org/protobuf/cmd/protoc-gen-go google.golang.org/grpc/cmd/protoc-gen-go-grpc
           

6. 创建相关的go文件

  1. 先进入proto目录:

    cd proto

  2. 创建*.pb.go和*_grpc.pb.go文件
protoc -I . --go_out ./ --go_opt paths=source_relative --go-grpc_out ./ --go-grpc_opt paths=source_relative ./*.proto
           
  1. 创建pb.gw.go文件
protoc -I . --grpc-gateway_out ./ --grpc-gateway_opt logtostderr=true --grpc-gateway_opt paths=source_relative --grpc-gateway_opt generate_unbound_methods=true ./*.proto
           
  1. 创建swagger.json文件(供UI访问服务)
protoc -I . --openapiv2_out ./gen/openapiv2 --openapiv2_opt logtostderr=true ./*.proto
           

测试

1. 编写server端HelloService服务的实现逻辑

protobuf提供的只是HelloService的接口,我们需要去在server端实现ServiceServer接口,实现SayHello的处理逻辑。

package service

import (
	"context"
	"log"
	"protobuf_demo/proto"
)

type MyHelloService struct {
	proto.UnimplementedServiceServer // must specify the field
}

// SayHello is in HelloService_grpc.pb.go
func (s MyHelloService) SayHello(ctx context.Context, req *proto.HelloServiceRequest) (*proto.HelloServiceResponse, error) {
	log.Printf("req: %v,rsp.Result:%v,req.Age:%v", req, req.Name,req.Age)
	result := "hello " + req.Name + ", your age is " + req.Age
	return &proto.HelloServiceResponse{
		Result: result,
	}, nil
}
           

2. 编写server和client

server实现

在server端,1088号端口监听grpc请求,1089号端口监听http请求。

package main

import (
	"context"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"golang.org/x/net/http2"
	"golang.org/x/net/http2/h2c"
	"google.golang.org/grpc"
	"log"
	"net"
	"net/http"
	"protobuf_demo/pkg/service"
	"protobuf_demo/proto"
	"strings"
)

func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
	return h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
			grpcServer.ServeHTTP(w, r)
		} else {
			otherHandler.ServeHTTP(w, r)
		}
	}), &http2.Server{})
}

func main() {
	lis, err := net.Listen("tcp", "localhost:1088")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	// 将service实现与server绑定
	s := grpc.NewServer()
	proto.RegisterServiceServer(s, service.MyHelloService{})

	// 提供http服务
	mux := runtime.NewServeMux()
	httpServerAndPort := "localhost:1089" // http服务监听本地1089端口
	// 注册proto文件中定义的http接口
	proto.RegisterServiceHandlerFromEndpoint(context.Background(), mux, httpServerAndPort, []grpc.DialOption{grpc.WithInsecure()})
	hs := &http.Server{
		Addr:    httpServerAndPort,
		Handler: grpcHandlerFunc(s, mux), // 将http请求转换为grpc请求,使用grpc处理
	}
	go func() {
		lis, _ := net.Listen("tcp", httpServerAndPort) // http服务也是监听tcp端口
		if err := hs.Serve(lis); err != nil {
			log.Fatalf("HTTP Server failed to serve: %v", err)
		}
	}()


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

           

client实现

package main

import (
	"context"
	"google.golang.org/grpc"
	"log"
	"protobuf_demo/proto"
)

func main() {
	// Set up a connection to the server.
	conn, err := grpc.Dial("localhost:1088", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := proto.NewServiceClient(conn)

	// Contact the server and print out its response.
	r, err := c.SayHello(context.Background(), &proto.HelloServiceRequest{Age: "18", Name: "cshen"})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Response: %s", r.Result)
}

           

3. client通过访问server

在IDE中先运行server的代码,再运行client的代码。

ProtoBuf之在GoLang中实现GRPC服务GRPC是什么环境准备测试

4. 终端通过http访问server

[email protected] protobuf_demo % curl "http://localhost:1089/say_hello?name=cshen&age=2"
{"result":"hello cshen, your age is 2"}%