【go语言】grpc 快速入门

一、什么是 grpc 和 protobuf

1.1 grpc

gRPC 是由 Google 开发的一个高效、开源的远程过程调用(RPC)框架,用于在分布式系统中进行通信。它是基于 HTTP/2 协议,支持多种语言,能够让不同的系统或应用程序(即使使用不同的编程语言)进行高效的通信。

1.1.1 主要特点

  • 高性能:gRPC 基于 HTTP/2 协议,支持流式传输、请求多路复用、头部压缩等特性,这些使得 gRPC 比传统的 HTTP/1.x 更加高效。
  • 跨语言支持:gRPC 支持多种编程语言(如 C++, Java, Go, Python, Ruby, Node.js 等),使得不同语言开发的系统能够无缝通信。
  • 支持同步和异步调用:gRPC 提供了同步和异步的调用方式,可以根据需求选择使用。
  • 内置支持流式传输:gRPC 支持单向流和双向流,可以非常方便地实现实时通信。
  • 自动生成代码:通过定义服务接口,gRPC 自动生成客户端和服务端的代码,减少手动编写的工作量。

1.1.2 典型应用场景

  • 微服务架构中的服务之间的高效通信。
  • 跨平台、跨语言的分布式系统通信。
  • 实时数据传输或流处理。

1.2 protobuf

Protocol Buffers(简称 Protobuf)是 Google 开发的一种高效的序列化结构数据的机制,类似于 XML 或 JSON,但比它们更紧凑、更高效。Protobuf 用于数据的定义、传输和持久化,通常与 gRPC 一起使用。

1.2.1 主要特点

  • 紧凑高效:Protobuf 使用二进制格式来存储数据,相比于 JSON 或 XML,它占用更少的空间,传输更快,解析也更高效。
  • 平台和语言无关:Protobuf 支持多种语言(如 C++, Java, Python, Go 等)并能在不同平台间进行无缝传输。
  • 结构化数据 :数据的定义通过 .proto 文件进行,可以清晰地指定数据的类型、字段以及数据结构。
  • 向后兼容与向前兼容:Protobuf 支持字段的增删和修改时,不影响已经存在的消息结构。新版本和旧版本可以兼容地进行通信。

1.2.2 工作原理

  1. 定义数据结构 :你需要创建一个 .proto 文件来定义数据结构和服务接口。例如:

    syntax = "proto3";
    
    message Person {
      string name = 1;
      int32 id = 2;
      string email = 3;
    }
    
    service Greeter {
      rpc SayHello (Person) returns (Person);
    }
    
  2. 编译 .proto 文件 :使用 protoc 编译器,将 .proto 文件转化为目标语言的代码。

  3. 生成代码 :Protobuf 会根据 .proto 文件生成语言特定的类和方法,可以通过这些类和方法来序列化、反序列化数据。

  4. 使用 gRPC :如果你定义了服务接口,gRPC 会根据 .proto 文件生成客户端和服务器端代码,帮助你构建高效的通信。

1.2.3 为什么结合使用 gRPC 和 Protobuf?

gRPC 通常与 Protobuf 一起使用,因为它们可以提供以下优势:

  • 性能:Protobuf 的二进制格式非常高效,适合需要高性能的场景,尤其是微服务通信时。
  • 简洁性:gRPC 和 Protobuf 的结合可以让你轻松地实现分布式服务,而无需担心数据格式的兼容性、传输效率等问题。
  • 自动化:通过 Protobuf 定义的数据结构和 gRPC 服务接口,所有的代码生成和数据序列化/反序列化都自动化处理,极大地减少了开发工作量。
  • gRPC 是一个高效的远程过程调用框架,用于不同系统之间的通信,支持多种语言,并且支持高效的流式传输。
  • Protobuf 是一种高效的序列化数据格式,通常用于 gRPC 中,能够减少数据的存储空间,提高传输效率。

这两者结合使用,可以构建出高效、可靠且跨语言的分布式系统。

二、RPC 有四种需求

2.1 简单模式

这种模式最为传统,即客户端发起一次请求,服务端响应一个数据,这个大家平时熟悉的RPC没有大的区别,所以不再详细介绍。

2.1.1 proto 文件

Go 复制代码
syntax = "proto3";

package helloworld;

// 指定 Go 包的路径
option go_package = ".;proto";

// 这是一个rpc服务
service Hello {
    rpc Hello(HelloRequest) returns (HelloResponse);
}

message HelloRequest {
    string name = 1; // 1 是编号
}

message HelloResponse {
    string reply = 1;
}

我们在编译 proto 文件的时候,我们需要先将安装了最新版本的 protoc-gen-goprotoc-gen-go-grpc 插件。如果你还没有安装或需要更新它们,可以使用以下命令:

Go 复制代码
# 安装最新的 protoc-gen-go 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

# 安装最新的 protoc-gen-go-grpc 插件
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

我们需要生成普通Go代码的时候,使用 protoc 命令时,命令格式如下,我们会根据 helloworld.proto 文件生成普通的 Go 代码文件。

cpp 复制代码
protoc -I . helloworld.proto --go_out=.

我们需要生成gRPC代码的时候,使用 protoc 命令时,命令格式如下,我们会使用 --go-grpc_out 来生成 gRPC 相关的代码(包括客户端和服务端的 gRPC 代码):

cpp 复制代码
protoc -I . helloworld.proto --go-grpc_out=.

我们可以使用完整的命令行,来一次性生成所有的代码(普通的 Go 代码和 gRPC 相关的代码),你可以将两个命令合并成一个:

cpp 复制代码
protoc -I . helloworld.proto --go_out=. --go-grpc_out=.

2.1.2 server端

Go 复制代码
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"log"
	"net"
	pb "xuego/protobuf/helloworld/proto"
)

type Server struct {
	pb.UnimplementedHelloServer // 继承自动生成的 Unimplemented 方法,可以确保将来不破坏接口兼容性
}

// SayHello 实现了 Greeter 服务的 SayHello 方法
func (s *Server) Hello(ctx context.Context, request *pb.HelloRequest) (*pb.HelloResponse, error) {
	// 构造并返回响应
	return &pb.HelloResponse{
		Reply: "Hello " + request.Name,
	}, nil
}

func main() {
	// 创建一个新的 gRPC 服务器
	g := grpc.NewServer()

	// 注册 Greeter 服务到 gRPC 服务器
	pb.RegisterHelloServer(g, &Server{})

	// 定义服务器监听地址和端口
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	// 输出日志,表明服务器开始监听
	fmt.Println("Server is listening on port :50051")

	// 启动 gRPC 服务器
	if err := g.Serve(listen); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

2.1.3 client端

Go 复制代码
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"xuego/protobuf/helloworld/proto"
)

func main() {
	conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	c := proto.NewHelloClient(conn)
	r, err := c.Hello(context.Background(), &proto.HelloRequest{Name: "xuego"})
	if err != nil {
		panic(err)
	}
	fmt.Errorf(r.Reply)
}

2.2 服务端数据流模式

这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。

2.3 客户端数据流模式

与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。典型的例子就是物联网终端向服务器报发数据。

2.4 双向数据流模式

顾名思义,这是客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是可以实现实时交互。典型的例子就是聊天机器人。

2.5 服务端数据流模式

&& 客户端数据流模式

&& 双向数据流模式

2.5.1 proto 文件

Go 复制代码
syntax = "proto3";

option go_package = ".;proto";

service Greeter {
    rpc GetStream(StreamReqData) returns (stream StreamResData); // 服务端流模式
    rpc PutStream(stream StreamReqData) returns (StreamResData); // 客户端流模式
    rpc AllStream(stream StreamReqData) returns (stream StreamResData); // 双向流模式
}

message StreamReqData {
    string data = 1;
}

message StreamResData {
    string data = 1;
}

在这些模式下,他们在服务端中写的函数的参数与简单模式的不同,需要注意一下!!!!

2.5.2 server端

Go 复制代码
package main

import (
	"fmt"
	"google.golang.org/grpc"
	"net"
	"sync"
	"time"
	pb "xuego/stream_grpc_test/proto"
)

const PORT = ":50052"

type Server struct {
	pb.UnimplementedGreeterServer
}

func (s *Server) GetStream(req *pb.StreamReqData, res pb.Greeter_GetStreamServer) error {
	i := 0
	for {
		i++
		_ = res.Send(&pb.StreamResData{
			Data: fmt.Sprintf("%v", time.Now().Unix()),
		})
		time.Sleep(time.Second)
		if i > 10 {
			break
		}
	}
	return nil
}

func (s *Server) PutStream(cliStr pb.Greeter_PutStreamServer) error {
	for {
		if a, err := cliStr.Recv(); err != nil {
			fmt.Println(err)
			break
		} else {
			fmt.Println(a.Data)
		}
	}
	return nil
}

func (s *Server) AllStream(allStr pb.Greeter_AllStreamServer) error {
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		for {
			data, _ := allStr.Recv()
			fmt.Println("收到客户端下消息:" + data.Data)
		}
	}()
	go func() {
		defer wg.Done()
		for {
			_ = allStr.Send(&pb.StreamResData{Data: "我是服务器"})
			time.Sleep(time.Second)
		}
	}()
	wg.Wait()
	return nil
}

func main() {
	lis, err := net.Listen("tcp", PORT)
	if err != nil {
		panic(err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &Server{})
	err = s.Serve(lis)
	if err != nil {
		panic(err)
	}
}

2.5.3 client端

Go 复制代码
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"sync"
	"time"
	pb "xuego/stream_grpc_test/proto"
)

func main() {
	conn, err := grpc.Dial("localhost:50052", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	defer conn.Close()
	// 服务端流模式
	c := pb.NewGreeterClient(conn)
	res, _ := c.GetStream(context.Background(), &pb.StreamReqData{Data: "陌客网"})
	for {
		a, err := res.Recv()
		if err != nil {
			fmt.Println(err)
			break
		}
		fmt.Println(a.Data)
	}

	// 客户端流模式
	putS, _ := c.PutStream(context.Background())
	i := 0
	for {
		i++
		putS.Send(&pb.StreamReqData{
			Data: fmt.Sprintf("慕课网%d", i),
		})
		time.Sleep(time.Second)
		if i > 10 {
			break
		}
	}

	// 双向流模式
	allStr, _ := c.AllStream(context.Background())
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		for {
			data, _ := allStr.Recv()
			fmt.Println("收到客户端下消息:" + data.Data)
		}
	}()
	go func() {
		defer wg.Done()
		for {
			_ = allStr.Send(&pb.StreamReqData{Data: "我是客户段"})
			time.Sleep(time.Second)
		}
	}()
	wg.Wait()
}
相关推荐
米码收割机11 分钟前
【python】tkinter实现音乐播放器(源码+音频文件)【独一无二】
开发语言·python·pygame
笛柳戏初雪26 分钟前
Python中的函数(下)
开发语言·python
美味小鱼36 分钟前
初识Cargo:Rust的强大构建工具与包管理器
开发语言·rust·cargo
码界筑梦坊42 分钟前
基于Django的个人博客系统的设计与实现
后端·python·django·毕业设计
c-c-developer1 小时前
C++ Primer 标准库类型string
开发语言·c++
山茶花开时。1 小时前
[SAP ABAP] Debug Skill
开发语言·sap·abap
奥顺互联V1 小时前
yes镜像站群/PHP驱动的镜像站群架构实践
开发语言·架构·开源·php
taopi20242 小时前
android java系统弹窗的基础模板
android·java·开发语言
釉色清风2 小时前
【matlab】绘图 离散数据--->连续函数
开发语言·matlab
松仔log2 小时前
Java多线程——对象的组合
java·开发语言·jvm