当前位置: 代码网 > it编程>前端脚本>Golang > Golang中四种gRPC模式举例详解

Golang中四种gRPC模式举例详解

2024年05月15日 Golang 我要评论
本博客需要你有一点基本的grpc的常识,如果你完全是新手建议访问官网全面了解。1. unary rpcproto文件如下syntax = "proto3";option go_package=".;s

本博客需要你有一点基本的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模式内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com