gRPC开发:go语言构建简单的服务端和客户端

准备工作


  • 安装protocol buffers
  1. 到protocol buffers到github到release页面下载指导压缩包(本文以mac 系统安装protocol buffers为栗子,其他系统方法类型)
  1. 下载完毕后解压并进入解压后到目录
    直接依次执行一下命令进行protocol buffers的安装
1. ./configure
2. make
3. make check
4. sudo make install
5. protoc --version 通过查看版本来测试是否安装成功

Hyperledger Fabric的go语言开发实战 go语言开发客户端_服务端

  • 安装本文需要的第三方go库
go get -u google.golang.org/grpc #安装gRPC库
go get -u github.com/golang/protobuf/protoc-gen-go #安装go语言的protoc插件

开始开发


  1. 创建如下格式的目录结构(本文结构并不是唯一,读者可以自行探寻更优的结构)

其中ecommerce目录是用来保存自动生成的存根文件

  1. 进入service目录执行如下命令来创建go的productinfo/service模块
go mod init productinfo/service

成功执行后会在service生成go.mod文件

Hyperledger Fabric的go语言开发实战 go语言开发客户端_客户端_02

  1. 定义服务,在ecommerce目录下创建product_info.proto并进行如下服务定义(客户端和服务端的服务定义相同)
syntax = "proto3";

package ecommerce;
option go_package = "../ecommerce";

service ProductInfo{
  rpc addProduct(Product) returns (ProductID);//定义添加产品
  rpc getProduct(ProductID) returns(Product);//定义获取产品,传入和返回参数只能有一个
}

//定义产品消息体,同一个消息体不能出现同样的编号
message Product {
  string id = 1;
  string name = 2;
  string description = 3;
  float price = 4 ;
}

//定义产品编号消息体
message ProductID{
  string value = 1;
}
  1. 在service目录下执行如下命令生成服务定义代码
protoc -I ecommerce ecommerce/product_info.proto --go_out=plugins=grpc:$PWD/ecommerce

Hyperledger Fabric的go语言开发实战 go语言开发客户端_服务端_03

  • 生成代码如图所示
  1. 开发服务端
    服务端目录结构

a. 服务端处理函数

// service.go

package main

import (
	"context"
	"github.com/gofrs/uuid"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	pb "productinfo/service/ecommerce"
)

// 用来实现ecommerce/product_info 的服务器
type server struct {
	productMap map[string]*pb.Product
}

/*
Context 对象包含一些元数据,比如终端用户授权令牌的标识和请求的截止时间,
这些元数据会在请求的生命周期内一直存在
*/

// AddProduct 实现ecommerce.AddProduct的AddProduct方法
func (s *server) AddProduct(ctx context.Context, in *pb.Product) (*pb.ProductID, error) {
	out, err := uuid.NewV4() //生成商品的UID
	if err != nil {
		return nil, status.Errorf(codes.Internal, "Error while generating Product ID", err)
	}
	in.Id = out.String()
	// 初始化服务,如果服务未初始化
	if s.productMap == nil {
		s.productMap = make(map[string]*pb.Product)
	}
	s.productMap[in.Id] = in
	return &pb.ProductID{Value: in.Id}, status.New(codes.OK, "").Err()
}

// GetProduct 实现ecommerce.GetProduct的GetProduct方法
func (s *server) GetProduct(ctx context.Context, in *pb.ProductID) (*pb.Product, error) {
	value, exists := s.productMap[in.Value]
	if exists {
		return value, status.New(codes.OK, "").Err()
	}
	return nil, status.Errorf(codes.NotFound, "Product does not exits")
}

b.服务端主函数

// main.go
package main

import (
	"google.golang.org/grpc"
	"log"
	"net"
	pb "productinfo/service/ecommerce"
)

const (
	port = ":50051"
)

func main() {
	lis, err := net.Listen("tcp", port) //在50051端口上创建gRPC服务
	if err != nil {
		log.Fatalf("failed to listen:%v", err)
	}
	s := grpc.NewServer()                      //创建新的gRPC实例
	pb.RegisterProductInfoServer(s, &server{}) //将生成的服务注册到注册到新的gRPC上
	log.Printf("Starting gRPC listener on port:%s", port)
	// 在指定端口上开始监听传入的消息
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve %v", err)
	}
}
  1. 开发客户端
    客户端目录结构

a.客户端主函数

//main.go
package main

import (
	"context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"log"
	pb "productinfo/client/ecommerce"
	"time"
)

const (
	address = "localhost:50051"
)

func main() {
	conn, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials())) //建立客户端和服务器之间的链接
	if err != nil {
		log.Fatalf("did not connect %v", err)
	}
	defer conn.Close()
	c := pb.NewProductInfoClient(conn) //传递连接并创建存根文件。这个实例包含可调用服务器的所有远程方法
	name := "MacBook Pro 2021"
	description := `Ultimate performance (极致性能)`
	price := float32(14000.0)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	// 远程调用AddProduct方法
	r, err := c.AddProduct(ctx, &pb.Product{Name: name, Description: description, Price: price})
	if err != nil {
		log.Fatalf("Could not add Product:%v", err)
	}
	log.Printf("Product ID:%s added successfully", r.Value)
	// 远程调用GetProduct方法
	product, err := c.GetProduct(ctx, &pb.ProductID{Value: r.Value})
	if err != nil {
		log.Fatalf("Could not get Product:%v", err)
	}
	log.Printf("Product: %v ", product.String())
}

运行结果


服务端

Hyperledger Fabric的go语言开发实战 go语言开发客户端_后端_04


客户端

Hyperledger Fabric的go语言开发实战 go语言开发客户端_grpc_05