Golang中四种gRPC模式举例详解

 更新时间:2024年03月30日 15:29:43   作者:dunzane  
gRPC是一种进程间通信技术,在微服务和云原生领域都有着广泛的应用,下面这篇文章主要给大家介绍了关于Golang中四种gRPC模式的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

本博客需要你有一点基本的gRPC的常识,如果你完全是新手建议访问官网全面了解。

1. Unary RPC

proto文件如下

syntax = "proto3";
option go_package=".;service";

message HelloRequest {
  // Name of the person to greet
  string name = 1;
}

message HelloResponse {
  // Greeting message
  string greeting = 1;
}

service HelloService {
  // RPC method to say hello
  rpc SayHello (HelloRequest) returns (HelloResponse){}
}

使用命令(注意命令路径和自己的对应):

protoc -I . --go-grpc_out=require_unimplemented_servers=false:. --go_out=.  *.proto

对应目录上有xx.pb.goxx_grpc.pb.go然后对应目录实现服务端接口

package main

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

type HelloService struct {
}

func (hs *HelloService) SayHello(ctx context.Context, req *service.HelloRequest) (*service.HelloResponse, error) {
	resp := &service.HelloResponse{
		Greeting: fmt.Sprintf("hello %s --from Golang Server", req.Name),
	}
	return resp, nil
}

func main() {
	// listen on 127.0.0.1:50051
	listen, err := net.Listen("tcp", "127.0.0.1:50051")
	if err != nil {
		fmt.Println("Error happened when listen on 127.0.0.1:50051:", err)
		return
	}

	// grpc server
	s := grpc.NewServer()

	// register HelloService in grpc server
	service.RegisterHelloServiceServer(s, &HelloService{})

	// start rpc server
	fmt.Println("Golang rpc server is waiting messages......")
	if err = s.Serve(listen); err != nil {
		fmt.Println("Error happened when start rpc server:", err)
		return
	}
}

客户端接口如下:

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"test_grpc/service"
	"time"
)

func main() {
	// connect to server
	conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Println("Connect to rpc server err:", err)
		return
	}
	defer conn.Close()

	// init service client
	c := service.NewHelloServiceClient(conn)

	// init context with timeout
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	// send message
	req := &service.HelloRequest{Name: "Golang"}
	r, err := c.SayHello(ctx, req)
	if err != nil {
		fmt.Println("Send message err:", err)
		return
	}
	fmt.Println("Client:", r.Greeting)
}

实际上为了更好得感受gRPC这种跨语言调用的感觉,可以尝试使用python编写client端代码,直接复制proto文件,在python中使用以下命令生成对应的proto文件(注意命令和自己的对应):

python -m grpc_tools.protoc -I . --python_out=. --pyi_out=. --grpc_python_out=. *.proto

使用python实现的客户端代码如下:

# client template
# grpc server address
channel = grpc.insecure_channel("127.0.0.1:50051")
stub = hello_pb2_grpc.HelloServiceStub(channel)

# send request
response = stub.SayHello(hello_pb2.HelloRequest(name="Python"))

print(response.greeting)
# hello Python --from Golang Server

2. Server-side streaming RPC

重新给出这个的proto文件,服务端将以流式数据的形式发送给客户端数据

syntax = "proto3";
option go_package=".;service";

message HelloRequest {
  // Name of the person to greet
  string name = 1;
}

message HelloResponse {
  // Greeting message
  string greeting = 1;
}

service HelloService {
  // RPC method to say hello
  rpc SayHello (HelloRequest) returns (stream HelloResponse){}
}

同理,生成对应的proto文件后,在对应的文件先生成server端的代码:

package main

import (
	"fmt"
	"google.golang.org/grpc"
	"net"
	"test_grpc/service"
)

type HelloService struct {
}

func (hs *HelloService) SayHello(req *service.HelloRequest, stream service.HelloService_SayHelloServer) error {
	resp := &service.HelloResponse{
		Greeting: fmt.Sprintf("hello %s --from Golang Server", req.Name),
	}
	// 连续发送5次
	for i := 0; i < 5; i++ {
		if err := stream.Send(resp); err != nil {
			return err
		}
	}
	return nil
}

func main() {
	// listen on 127.0.0.1:50051
	listen, err := net.Listen("tcp", "127.0.0.1:50051")
	if err != nil {
		fmt.Println("Error happened when listen on 127.0.0.1:50051:", err)
		return
	}

	// grpc server
	s := grpc.NewServer()

	// register HelloService in grpc server
	service.RegisterHelloServiceServer(s, &HelloService{})

	// start rpc server
	fmt.Println("Golang rpc server is waiting messages......")
	if err = s.Serve(listen); err != nil {
		fmt.Println("Error happened when start rpc server:", err)
		return
	}
}

同理给出客户端的代码:

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"io"
	"log"
	"test_grpc/service"
	"time"
)

func main() {
	// connect to server
	conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Println("Connect to rpc server err:", err)
		return
	}
	defer conn.Close()

	// init service client
	c := service.NewHelloServiceClient(conn)

	// init context with timeout
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	// send message
	req := &service.HelloRequest{Name: "Golang"}
	stream, err := c.SayHello(ctx, req)
	if err != nil {
		fmt.Println("Send message err:", err)
		return
	}

	// 加载消息
	for {
		resp, err := stream.Recv()
		// 读到结束标志
		if err == io.EOF {
			log.Fatalf("end.....")
			break
		}

		if err != nil {
			log.Fatalf("failed to receive response: %v", err)
		}

		log.Printf("Greeting: %s", resp.Greeting)
	}
}

3. Client-side streaming RPC

对应的proto文件如下

syntax = "proto3";
option go_package=".;service";

message HelloRequest {
  // Name of the person to greet
  string name = 1;
}

message HelloResponse {
  // Greeting message
  string greeting = 1;
}

service HelloService {
  // RPC method to say hello
  rpc SayHello (stream HelloRequest) returns (HelloResponse){}
}

同理使用protoc命令生成对应的proto文件,后先编写client端的代码,如下:

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"log"
	"test_grpc/service"
	"time"
)

func main() {
	// connect to server
	conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Println("Connect to rpc server err:", err)
		return
	}
	defer conn.Close()

	// init service client
	c := service.NewHelloServiceClient(conn)

	// init context with timeout
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	// create stream

	stream, err := c.SayHello(ctx)
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}

	names := []string{"World", "Gophers", "Anthropic"}

	for _, name := range names {
		// request body
		req := &service.HelloRequest{Name: name}
		if err := stream.Send(req); err != nil {
			log.Fatalf("faild to send request: %v", err)
		}
	}

	resp, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
	}
	log.Printf("Greeting: %s", resp.Greeting)
}

对应得完成服务端的代码:

package main

import (
	"fmt"
	"google.golang.org/grpc"
	"io"
	"net"
	"strings"
	"test_grpc/service"
)

type HelloService struct {
}

func (hs *HelloService) SayHello(stream service.HelloService_SayHelloServer) error {
	var strs []string
	for {
		msg, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}

		strs = append(strs, msg.Name)
	}

	resp := &service.HelloResponse{Greeting: strings.Join(strs, " ")}

	err := stream.SendAndClose(resp)
	if err != nil {
		return err
	}
	return nil
}

func main() {
	// listen on 127.0.0.1:50051
	listen, err := net.Listen("tcp", "127.0.0.1:50051")
	if err != nil {
		fmt.Println("Error happened when listen on 127.0.0.1:50051:", err)
		return
	}

	// grpc server
	s := grpc.NewServer()

	// register HelloService in grpc server
	service.RegisterHelloServiceServer(s, &HelloService{})

	// start rpc server
	fmt.Println("Golang rpc server is waiting messages......")
	if err = s.Serve(listen); err != nil {
		fmt.Println("Error happened when start rpc server:", err)
		return
	}
}

4. Bidirectional streaming RPC

新的proto文件被如下给出:

syntax = "proto3";
option go_package=".;service";

message HelloRequest {
  // Name of the person to greet
  string name = 1;
}

message HelloResponse {
  // Greeting message
  string greeting = 1;
}

service HelloService {
  // RPC method to say hello
  rpc SayHello (stream HelloRequest) returns (stream HelloResponse){}
}

和上文中的操作一致,同时给出server端的代码:

package main

import (
	"fmt"
	"google.golang.org/grpc"
	"io"
	"log"
	"net"
	"test_grpc/service"
)

type HelloService struct {
}

func (hs *HelloService) SayHello(stream service.HelloService_SayHelloServer) error {
	for {
		msg, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}

		name := msg.Name
		resp := &service.HelloResponse{Greeting: name}

		if err = stream.Send(resp); err != nil {
			log.Fatalf("Failed to send a resp:%s", err)
		}
	}

	return nil
}

func main() {
	// listen on 127.0.0.1:50051
	listen, err := net.Listen("tcp", "127.0.0.1:50051")
	if err != nil {
		fmt.Println("Error happened when listen on 127.0.0.1:50051:", err)
		return
	}

	// grpc server
	s := grpc.NewServer()

	// register HelloService in grpc server
	service.RegisterHelloServiceServer(s, &HelloService{})

	// start rpc server
	fmt.Println("Golang rpc server is waiting messages......")
	if err = s.Serve(listen); err != nil {
		fmt.Println("Error happened when start rpc server:", err)
		return
	}
}

同时给出下面的client端的代码:

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"io"
	"log"
	"test_grpc/service"
	"time"
)

func main() {
	// connect to server
	conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Println("Connect to rpc server err:", err)
		return
	}
	defer conn.Close()

	// init service client
	c := service.NewHelloServiceClient(conn)

	// init context with timeout
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	// create stream
	stream, err := c.SayHello(ctx)
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}

	names := []string{"World", "Gophers", "Anthropic"}

	waitc := make(chan struct{})
	go func() {
		for {
			resp, err := stream.Recv()
			if err == io.EOF {
				close(waitc)
				return
			}
			if err != nil {
				log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
			}
			log.Printf("Greeting: %s", resp.Greeting)
		}
	}()

	go func() {
		for _, name := range names {
			// request body
			req := &service.HelloRequest{Name: name}
			if err := stream.Send(req); err != nil {
				log.Fatalf("faild to send request: %v", err)
			}

			// send delay
			time.Sleep(1)
		}
		// 发送结束的消息
		if err := stream.CloseSend(); err != nil {
			log.Fatalf("failed to close stream: %v", err)
		}
	}()

	<-waitc
}

一定要注意关闭发送或者避免针对一个已经关闭stream进行发送消息,读取消息是被允许的,这里有一点类似chan

4. ALTS

4.1 ALTS的介绍

应用层传输安全(ALTS)是谷歌开发的一种相互验证和传输加密系统。它用于确保谷歌基础设施内 RPC 通信的安全。ALTS 类似于相互 TLS,但经过设计和优化,可满足 Google 生产环境的需要。ALTS在gRPC中有以下的特征:

  • 使用ALTS作为传输协议创建gRPC的服务端和客户端;
  • ALSTS是一个端到端的保护,具有隐私性和完成性;
  • 应用可以访问对等信息比如对等服务账户;
  • 支持客户端和服务端的认知;
  • 最小的代码更改就能使用ALTS;

值得注意的是ALTS被全部发挥作用如果应用程序运行在CE或者GKE中

4.2 gRPC客户端使用ALTS传输安全协议

gRPC客户端使用ALTS认证去连接服务端,正如下面代码中所描述的:

import (
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/alts"
)

altsTC := alts.NewClientCreds(alts.DefaultClientOptions())
// connect to server
conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(altsTC))

gRPC服务端能够使用ALTS认证来运行客户端连接到它,正如下面的描述:

import (
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/alts"
)

altsTC := alts.NewServerCreds(alts.DefaultServerOptions())
server := grpc.NewServer(grpc.Creds(altsTC))

4.3 Server Authorization

gRPC 内置了使用 ALTS 的服务器授权支持。使用 ALTS 的 gRPC 客户端可以在建立连接前设置预期的服务器服务账户。然后,在握手结束时,服务器授权会保证服务器身份与客户端指定的服务账户之一相匹配。否则,连接将失败。

import (
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/alts"
)

clientOpts := alts.DefaultClientOptions()
clientOpts.TargetServiceAccounts = []string{expectedServerSA}
altsTC := alts.NewClientCreds(clientOpts)
conn, err := grpc.Dial(serverAddr, grpc.WithTransportCredentials(altsTC))

总结  

到此这篇关于Golang中四种gRPC模式的文章就介绍到这了,更多相关Golang中gRPC模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解使用Go添加Nginx代理的方法示例

    详解使用Go添加Nginx代理的方法示例

    这篇文章主要介绍了详解使用Go添加Nginx代理的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • 详解Go语言如何实现类似Python中的with上下文管理器

    详解Go语言如何实现类似Python中的with上下文管理器

    熟悉 Python 的同学应该知道 Python 中的上下文管理器非常好用,那么在 Go 中是否也能实现上下文管理器呢,下面小编就来和大家仔细讲讲吧
    2023-07-07
  • Golang实现Java虚拟机之解析class文件详解

    Golang实现Java虚拟机之解析class文件详解

    这篇文章主要为大家详细介绍了Golang实现Java虚拟机之解析class文件的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-01-01
  • Golong字符串拼接性能优化及原理介绍

    Golong字符串拼接性能优化及原理介绍

    最近在做性能优化,有个函数里面的耗时特别长,看里面的操作大多是一些字符串拼接的操作,而字符串拼接在 golang 里面其实有很多种实现,下面这篇文章主要给大家介绍了关于Golang语言如何高效拼接字符串的相关资料,需要的朋友可以参考下
    2023-04-04
  • 手把手带你走进Go语言之条件表达式

    手把手带你走进Go语言之条件表达式

    条件表达式由条件运算符构成,并常用条件表达式构成一个赋值语句,本文给大家介绍了在Go语言中条件表达式的具体用法,讲述的非常详细,对大家的学习或工作具有一定的参考借鉴价值
    2021-09-09
  • Golang中的路由使用详解

    Golang中的路由使用详解

    这篇文章主要介绍了Golang中的路由使用详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-06-06
  • Go语言入门之基础语法和常用特性解析

    Go语言入门之基础语法和常用特性解析

    这篇文章主要给大家讲解了Go语言的基础语法和常用特性解析,比较适合入门小白,文中通过代码示例介绍的非常详细,对我们学习Go语言有一定的帮助,需要的朋友可以参考下
    2023-07-07
  • Golang 如何解析和生成json

    Golang 如何解析和生成json

    这篇文章主要介绍了Golang 如何解析和生成json,帮助大家更好的理解和学习golang,感兴趣的朋友可以了解下
    2020-09-09
  • Go语言变量的声明实现示例

    Go语言变量的声明实现示例

    本文主要介绍了Go语言变量的声明实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-03-03
  • golang判断两个事件是否存在冲突的方法示例

    golang判断两个事件是否存在冲突的方法示例

    这篇文章主要为大家详细介绍了golang判断两个事件是否存在冲突的方法示例,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-10-10

最新评论