跳至主要內容

gRPC入门

Chiichen原创大约 3 分钟笔记后端后端gRPCgo

什么是 gRPC

前期准备

在本篇内容中,我们将通过go+gin+gRPC的组合来初步认识 gRPC,并实现一个简单的网关应用

Go 环境安装

创建项目

mkdir app-gateway
cd app-gateway
go mod init app-gateway

安装依赖项

go get -u google.golang.org/grpc
sudo apt install protobuf-compiler # 安装 protobuf 编译器
go install github.com/golang/protobuf/protoc-gen-go@latest # 安装go编译插件
sudo cp -r $GOPATH/bin/protoc-gen-go /usr/bin # 或者把 $GOPATH/bin 添加到 PATH

定义 gRPC 服务

定义 gRPC 服务实际上就是编写.proto文件并编译为对应高级语言的过程

构建 proto 文件

我们新建如下的项目结构

.
├── go.mod
├── go.sum
└── src
    ├── cmd
    │   ├── client
    │   │   └── client.go
    │   └── server
    │       └── server.go
    ├── proto
    │   ├── message.pb.go
    │   └── message.proto
    └── server
        └── server.go

message.proto就是我们的 proto 文件,有如下内容

syntax = "proto3";
//option go_package = "path;name";
//path 表示生成的go文件的存放地址,会自动生成目录的。
//name 表示生成的go文件所属的包名
option go_package="./;proto";
// 定义包名
package proto;

service MessageService {
    rpc SendMessage (SendMessageRequest) returns (SendMessageResponse);
    rpc ReceiveMessage (ReceiveMessageRequest) returns (ReceiveMessageResponse);
}

enum SendMessageStatus{
    Success=0;
    MessageExist=1;
    Failed=2;
}

message SendMessageRequest {
    uint64 id=1;
    string content=2;
}

message SendMessageResponse {
    SendMessageStatus status=1;
}

message ReceiveMessageRequest {
    uint64 id=1;
}

message ReceiveMessageResponse {
    string content=1;
}

Highlight

vscode 中可以使用插件vscode-proto3获得高亮提示

编译 proto 文件

编译指令

protoc --proto_path=IMPORT_PATH  --go_out=OUT_DIR  --go_opt=paths=source_relative path/to/file.proto
  • proto_path 或者-I :指定 import 路径,可以指定多个参数,编译时按顺序查找,不指定时默认查找当前目录。
    • proto 文件中也可以引入其他 .proto 文件,这里主要用于指定被引入文件的位置。
  • go_out:golang 编译支持,指定输出文件路径
  • go_opt:指定参数,比如--go_opt=paths=source_relative 就是表明生成文件输出使用相对路径。
  • path/to/file.proto :被编译的 .proto 文件放在最后面

对于我们的项目,执行下述命令进行编译

protoc -I ./src/proto/ --go_out=plugins=grpc:./src/proto ./src/proto/message.proto

参数说明

  • -I 指定代码输出目录,忽略服务定义的包名,否则会根据包名创建目录
  • --go_out 指定代码输出目录,格式:--go_out=plugins=grpc:目录名
  • plugins=grpc 表示启用 rpc,并且指定是 grpc
  • 命令最后面的参数是 proto 协议文件 编译成功后在 proto 目录生成了 helloworld.pb.go 文件,里面包含了,我们的服务和接口定义。

实现 gRPC 服务

在项目/src 目录下下创建一个名为server的文件夹,并在其中创建一个名为server.go的文件。这个文件将实现我们的 gRPC 服务。

package server

import (
	"context"
	"fmt"
	"grpc-demo/src/proto"
	"log"
)

type MessageServer struct {
	contentMap map[int64]string
}

func (s *MessageServer) SendMessage(ctx context.Context, req *proto.SendMessageRequest) (*proto.SendMessageResponse, error) {
	log.Printf("Sending %s", req)
	_, ok := s.contentMap[int64(req.Id)]
	if ok {
		return &proto.SendMessageResponse{Status: proto.SendMessageStatus_MessageExist}, nil
	} else {
		s.contentMap[int64(req.Id)] = req.Content
	}
  // 以上可以替换成自己的逻辑
	return &proto.SendMessageResponse{Status: proto.SendMessageStatus_Success}, nil
}

func (s *MessageServer) ReceiveMessage(ctx context.Context, req *proto.ReceiveMessageRequest) (*proto.ReceiveMessageResponse, error) {
	log.Printf("Receiving %s", req)
	v, ok := s.contentMap[int64(req.Id)]
	if ok {
		return &proto.ReceiveMessageResponse{Content: v}, nil
	} else {
		return &proto.ReceiveMessageResponse{Content: "No Message"}, fmt.Errorf("No Message for id %d", req.Id)
	}
  // 以上可以替换成自己的逻辑
}

func NewMessageServer() *MessageServer {
	return &MessageServer{contentMap: map[int64]string{}}
}

实现 Server

我们还需要一个 server 来承载 grpc

// src/cmd/server.go
package main

import (
	"fmt"
	"log"
	"net"

	pb "grpc-demo/src/proto"
	"grpc-demo/src/server"

	"google.golang.org/grpc"
)

const port = 8080

func main() {
	lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	var opts []grpc.ServerOption
	grpcServer := grpc.NewServer(opts...)
	pb.RegisterMessageServiceServer(grpcServer, server.NewMessageServer())
	grpcServer.Serve(lis)
}

实现 Client

// src/cmd/client
package main

import (
	"context"
	"log"

	"google.golang.org/grpc"

	pb "grpc-demo/src/proto"
)

const PORT = "8080"

func main() {
	conn, err := grpc.NewClient(":"+PORT, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("grpc.Dial err: %v", err)
	}
	defer conn.Close()

	client := pb.NewMessageServiceClient(conn)
	resp, err := client.SendMessage(context.Background(), &pb.SendMessageRequest{
		Id:      0,
		Content: "Client sending content",
	})
	log.Printf("Seding resp: %s", resp.String())
	if err != nil {
		log.Fatalf("client.send err: %v", err)
	}
	receiveResp, err := client.ReceiveMessage(context.Background(), &pb.ReceiveMessageRequest{
		Id: 0,
	})
	if err != nil {
		log.Fatalf("client.receive err: %v", err)
	}
	log.Printf("Receive resp: %s", receiveResp.String())
}

运行

首先,启动 gRPC 服务:

go run src/cmd/server.go

然后,启动 gRPC 客户端:

go run src/cmd/client.go