Administrator
Administrator
Published on 2024-12-29 / 19 Visits
0
0

protobuf和gRPC

protobuf(protocol buffer)是google开发的数据序列化协议,特点有高效、轻量级、用来在不同的系统之间交换结构化数据,尤其适合网络通信和存储。pb序列化后是紧凑的二进制格式

gRPC是由google开发的高效远程调用框架。

以下用golang来写gRPC服务

安装pb编绎器

不同编程语言在生成时有各自的工具,但是核心是依赖于protoc,所以先安装它

github release

找到自己系统对应的文件进行下载(osx是macOS),然后解压到一个文件夹中

windows的压缩包下载后包含二个文件夹:bin、include,将bin目录添加到环境变量中。

protoc --version #查看是否会输出版本号

安装golang工具

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

工具会安装在%GOPATH%/bin中(GOPATH可以通过go env GOPATH命令获取),windows中GOPATH默认为:C:\Users\<user>\go

编绎、实现业务、调用

下载依赖

go get google.golang.org/grpc
go get google.golang.org/protobuf

proto文件

syntax = "proto3";

package microservice;
option go_package = ".grpc";

service ExampleService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
  rpc SayGoodbye (GoodbyeRequest) returns (GoodbyeResponse);
}

message HelloRequest {
  string name = 1;
}
message HelloResponse {
  string message = 1;
}


message GoodbyeRequest {
  string name = 1;
}
message GoodbyeResponse {
  string message = 1;
}

共有二个方法(API):SayHello、SayGoodbye,message定义数据结构

假设文件名为:exam.proto

protoc --go_out=. --go-grpc_out=. exam.proto

执行后会新建一个目录名称:.grpc

里面有二个文件:exam.pb.go、exam_grpc.pb.go,这二个文件的package为:__grpc,这二个文件不需要改动

实现业务

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	__grpc "grpc/.grpc"
	"net"
)

// 实现生成出来的exam_grpc.pb.go中的type ExampleServiceServer interface {}
type ExampleServiceServerImpl struct {
	__grpc.UnimplementedExampleServiceServer
}

func (e ExampleServiceServerImpl) SayGoodbye(ctx context.Context, request *__grpc.GoodbyeRequest) (*__grpc.GoodbyeResponse, error) {
	return &__grpc.GoodbyeResponse{
		Message: "Hello, " + request.Name + " !",
	}, nil
}

func (e ExampleServiceServerImpl) SayHello(ctx context.Context, request *__grpc.HelloRequest) (*__grpc.HelloResponse, error) {
	return &__grpc.HelloResponse{
		Message: "Hello, " + request.Name + " !",
	}, nil
}

func main() {
	//布署到生产环境时,可以这样创建TLS安全连接服务端
	//pem, err := credentials.NewServerTLSFromFile("./cert/1.pem", "./cert/1.key")
	//if err != nil {
	//	fmt.Print("加载pem失败", err)
	//	return
	//}
	//grpcServer := grpc.NewServer(grpc.Creds(pem))

	//非安全连接,明文传输
	grpcServer := grpc.NewServer()

	__grpc.RegisterExampleServiceServer(grpcServer, &ExampleServiceServerImpl{})
	listener, err := net.Listen("tcp", ":8800")
	fmt.Println("Listen Err", err)
	err = grpcServer.Serve(listener)
}

客户端

如果是泛域名证书,要先解析一个二级域名,newClient时传入xxxx.exmaple.com:8800

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	__grpc "grpc/.grpc"
)

func main() {
	//使用安全连接,1.pem为公钥文件(xxx.com要换成自己的域名),客户端不需要私钥,只需要pem公钥即可
    //对应的8800端口需要在安全组中开放
	//pem, err := credentials.NewClientTLSFromFile("./cert/1.pem", "")
	//if err != nil {
	//	fmt.Print("加载pem失败", err)
	//	return
	//}
	//conn, err := grpc.NewClient("xxx.com:8800", grpc.WithTransportCredentials(pem))


	// 使用非安全连接(开发环境)
	conn, err := grpc.NewClient("localhost:8800", grpc.WithTransportCredentials(insecure.NewCredentials()))
	fmt.Println(err)
	client := __grpc.NewExampleServiceClient(conn)

	var ret *__grpc.HelloResponse
	ret, err = client.SayHello(context.Background(), &__grpc.HelloRequest{
		Name: "test",
	})
	fmt.Println(ret, err)

	var ret2 *__grpc.GoodbyeResponse
	ret2, err = client.SayGoodbye(context.Background(), &__grpc.GoodbyeRequest{
		Name: "test2",
	})
	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println(ret2)
	}
}

node.js客户端

安装依赖

npm install @grpc/grpc-js @grpc/proto-loader

将golang项目中的exam.proto拷贝到Node.js项目中

代码:

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

// 加载 .proto 文件
const packageDefinition = protoLoader.loadSync('exam.proto', {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});
const myProto = grpc.loadPackageDefinition(packageDefinition).microservice; // microservice 是 .proto 文件中定义的 package 名称


const client = new myProto.ExampleService('localhost:8800', grpc.credentials.createInsecure()); // 8800 是服务端的端口, ExampleService是.proto文件中定义的服务名称

client.SayHello({ name: "self" }, (err, response) => {
  console.log(response, err);
})


Comment