go语言中protobuf以及grpc的使用

发布时间 2023-06-03 08:31:58作者: 念秋

首先定义数据结构,保存为.proto文件

syntax = "proto3";

package tutorial;

// The protocol compiler generates a class from the following .proto file with
// methods for writing and reading the message fields. The class is called
// "Person" and is in a package called "tutorial". You can specify a different
// class name using the java_outer_classname option.

message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}

然后,用protocol buffer编译器以生成go语言源代码。没想多遇见了好几次问题。当然,已经有现成且有效的解决方案了。

编译指令:

protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto
首先报错的是,没有protoc-gen-go。于是百度,搜到了下面这篇博客。
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
没有程序,首先就要安装。使用上条指令安装后,需要手动复制protoc-gen-go.exe到proto的bin路径下。
随后,又执行上面的编译指令,就出现了下面的问题。
protoc-gen-go: unable to determine Go import path for "person.proto"

Please specify either:
        • a "go_package" option in the .proto source file, or
        • a "M" argument on the command line.
See https://protobuf.dev/reference/go/go-generated#package for more information.
解决方案是在proto文件中添加一行
option go_package="/main";也就是需要自己设定生成的pb.go的包名

解决方案,这篇博客非常详细,清楚,也可以直接查看。

main.go

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/proto"
	"io/ioutil"
	"log"
	"net"
	"protoTest/calculator"
	"protoTest/person"
)

const fileName = "D:\\protoTest\\main\\addressbook.data"

func testProc() {

	in, err := ioutil.ReadFile(fileName)
	if err != nil {
		log.Fatalln("Error reading file:", err)
	}
	book := &person.AddressBook{}
	if err := proto.Unmarshal(in, book); err != nil {
		log.Fatalln("Failed to parse address book:", err)
	}

	// Add an address.
	book.People = append(book.People, &person.Person{
		Name:  "Alice",
		Id:    100,
		Email: "alice@example.com",
	})

	// Write the new address book back to disk.
	out, err := proto.Marshal(book)
	if err != nil {
		log.Fatalln("Failed to encode address book:", err)
	}
	if err := ioutil.WriteFile(fileName, out, 0644); err != nil {
		log.Fatalln("Failed to write address book:", err)
	}
	fmt.Println("that is ok")
}

func main() {
	testProc()
}

通过简短的例子和bing的解释,大致了解了protobuf的优势。以二进制的方式压缩数据,在传输过程中就能节省资源,减轻网络负荷。

例子比较简易,然后就想试一下protobuf在grpc中的使用,就有了下面的例子。

calculator.proto

package calculator;

// The calculator service definition.
service Calculator {
  // Sends two integers and returns their sum
  rpc Add (AddRequest) returns (AddResponse) {}
  // Sends two integers and returns their difference
  rpc Subtract (SubtractRequest) returns (SubtractResponse) {}
  // Sends two integers and returns their product
  rpc Multiply (MultiplyRequest) returns (MultiplyResponse) {}
  // Sends two integers and returns their quotient
  rpc Divide (DivideRequest) returns (DivideResponse) {}
}

// The request message containing two integers
message AddRequest {
  int32 a = 1;
  int32 b = 2;
}

// The response message containing the sum
message AddResponse {
  int32 result = 1;
}

// The request message containing two integers
message SubtractRequest {
  int32 a = 1;
  int32 b = 2;
}

// The response message containing the difference
message SubtractResponse {
  int32 result = 1;
}

// The request message containing two integers
message MultiplyRequest {
  int32 a = 1;
  int32 b = 2;
}

// The response message containing the product
message MultiplyResponse {
  int32 result = 1;
}

// The request message containing two integers
message DivideRequest {
  int32 a = 1;
  int32 b = 2;
}

// The response message containing the quotient
message DivideResponse {
  int32 result = 1;
}

protoc -I=D:\Git\protoTest\protobuf --go_out=D:\Git\protoTest calculator.proto

没注意,以为都是用protoc-gen-go.exe就好了呢。直接编译,还是生成了一堆文件,没有报错。
但是,发觉和官方给的例子差异很大。比如,官方的helloworld例子,我直接看的在线代码,里面proto生成的pb.go包含客户端和服务器的部分,但是我的pb.go文件就没有。我还觉得很奇怪呢,但还是有样学样的,添加一些代码,终于改好了client。于是开始改server端,但是太麻烦了,越改越觉得离谱。于是重新看了下bing给我的编译指令,和我这个可差太远了。才意识到问题所在。
protoc calculator.proto --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative

使用这个指令编译,遇见的问题,就是类似于上面的,没有protoc-gen-go-grpc。

于是有样学样,安装就好了。

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

同理,安装后需要复制protoc-gen-go-grpc.exe到protoc的bin路径下。

然后就可以正常编译了。

protoc calculator.proto --go_out=..  --go-grpc_out=..	//修改了生成文件保存的路径。

server.go

package main

import (
	"context"
	"grpcTest/calculator"
)

type Server struct {
	calculator.UnimplementedCalculatorServer
}

// Add implements the Add rpc method
func (s *Server) Add(ctx context.Context, in *calculator.AddRequest) (*calculator.AddResponse, error) {
	// Get the two integers from the request
	a := in.GetA()
	b := in.GetB()
	// Compute the sum
	result := a + b
	// Return the response with the sum
	return &calculator.AddResponse{Result: result}, nil
}

func (s *Server) Subtract(ctx context.Context, in *calculator.SubtractRequest) (*calculator.SubtractResponse, error) {
	// Get the two integers from the request
	a := in.GetA()
	b := in.GetB()
	// Compute the sum
	result := a - b
	// Return the response with the sum
	return &calculator.SubtractResponse{Result: result}, nil
}
func (s *Server) Multiply(ctx context.Context, in *calculator.MultiplyRequest) (*calculator.MultiplyResponse, error) {
	// Get the two integers from the request
	a := in.GetA()
	b := in.GetB()
	// Compute the sum
	result := a * b
	// Return the response with the sum
	return &calculator.MultiplyResponse{Result: result}, nil
}
func (s *Server) Divide(ctx context.Context, in *calculator.DivideRequest) (*calculator.DivideResponse, error) {
	// Get the two integers from the request
	a := in.GetA()
	b := in.GetB()
	// Compute the sum
	result := a / b
	// Return the response with the sum
	return &calculator.DivideResponse{Result: result}, nil
}

client.go

package main

import (
	"context"
	"google.golang.org/grpc"
	"grpcTest/calculator"
)

type Client struct {
	conn *grpc.ClientConn
}

// NewClient creates a new Client with the given connection
func NewClient(conn *grpc.ClientConn) *Client {
	return &Client{conn: conn}
}

// Add calls the Add rpc method with two integers and returns their sum
func (c *Client) Add(a, b int32) (int32, error) {
	// Create a new CalculatorClient with the connection
	//client := NewCalculatorClient(c.conn)
	client := calculator.NewCalculatorClient(c.conn)
	// Create a new AddRequest with the two integers
	req := &calculator.AddRequest{A: a, B: b}
	// Call the Add method with the request and get the response
	res, err := client.Add(context.Background(), req)
	if err != nil {
		return 0, err
	}
	// Return the result from the response
	return res.GetResult(), nil
}

func (c *Client) Subtract(a, b int32) (int32, error) {
	// Create a new CalculatorClient with the connection
	//client := NewCalculatorClient(c.conn)
	client := calculator.NewCalculatorClient(c.conn)
	// Create a new AddRequest with the two integers
	req := &calculator.SubtractRequest{A: a, B: b}
	// Call the Add method with the request and get the response
	res, err := client.Subtract(context.Background(), req)
	if err != nil {
		return 0, err
	}
	// Return the result from the response
	return res.GetResult(), nil
}

func (c *Client) Multiply(a, b int32) (int32, error) {
	// Create a new CalculatorClient with the connection
	//client := NewCalculatorClient(c.conn)
	client := calculator.NewCalculatorClient(c.conn)
	// Create a new AddRequest with the two integers
	req := &calculator.MultiplyRequest{A: a, B: b}
	// Call the Add method with the request and get the response
	res, err := client.Multiply(context.Background(), req)
	if err != nil {
		return 0, err
	}
	// Return the result from the response
	return res.GetResult(), nil
}

func (c *Client) Divide(a, b int32) (int32, error) {
	// Create a new CalculatorClient with the connection
	//client := NewCalculatorClient(c.conn)
	client := calculator.NewCalculatorClient(c.conn)
	// Create a new AddRequest with the two integers
	req := &calculator.DivideRequest{A: a, B: b}
	// Call the Add method with the request and get the response
	res, err := client.Divide(context.Background(), req)
	if err != nil {
		return 0, err
	}
	// Return the result from the response
	return res.GetResult(), nil
}

// Close closes the connection
func (c *Client) Close() error {
	return c.conn.Close()
}

main.go

package main

import (
	"context"
	"google.golang.org/grpc"
	"grpcTest/calculator"
	"log"
	"net"
)

const address = "localhost:50051"
const (
	// Port on which the server listens.
	port = ":50051"
)

func testServer(c chan bool) {
	// Listen on a port
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	// Create a new gRPC server
	s := grpc.NewServer()
	// Register a new Calculator service with the server
	calculator.RegisterCalculatorServer(s, &Server{})
	c <- true
	log.Println("server listening at :50051")
	// Serve gRPC requests on the port
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

func test() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	// Create a new gRPC server
	s := grpc.NewServer()
	// Register a new Calculator service with the server
	calculator.RegisterCalculatorServer(s, &Server{})
	log.Println("server listening at :50051")
	// Serve gRPC requests on the port
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
func main() {
	test()
	return
	//testProc()
	ch := make(chan bool)
	go testServer(ch)

	conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()

	// Create a calculator client
	c := calculator.NewCalculatorClient(conn)

	// Call the Add method with two integers
	r, err := c.Add(context.Background(), &calculator.AddRequest{A: 3, B: 5})
	if err != nil {
		log.Fatalf("could not add: %v", err)
	}
	// Print the result
	log.Printf("The sum is: %d", r.GetResult())

	r1, err := c.Subtract(context.Background(), &calculator.SubtractRequest{A: 3, B: 5})
	if err != nil {
		log.Fatalf("could not add: %v", err)
	}
	// Print the result
	log.Printf("The sum is: %d", r1.GetResult())

	r2, err := c.Multiply(context.Background(), &calculator.MultiplyRequest{A: 3, B: 5})
	if err != nil {
		log.Fatalf("could not add: %v", err)
	}
	// Print the result
	log.Printf("The sum is: %d", r2.GetResult())

	r3, err := c.Divide(context.Background(), &calculator.DivideRequest{A: 13, B: 5})
	if err != nil {
		log.Fatalf("could not add: %v", err)
	}
	// Print the result
	log.Printf("The sum is: %d", r3.GetResult())
}

过程中缺什么包啥的,直接go get就好了。一执行,还是没有任何问题的。

这是目录结构:

image-20230602215300462

于是又想着,protobuf是可以支持不同平台的。所以还想弄个python请求go的例子

没想到处处碰壁,python/Java/C++/node。都很困难。暂时就先不弄了。