grpc的简单用例(golang实现)(代码片段)

albizzia albizzia     2022-12-08     228

关键词:

这个用例的逻辑很简单, 服务器运行一个管理个人信息的服务, 提供如下的四个服务:

(1) 添加一个个人信息  

注: 对应于Unary RPCs, 客户端发送单一消息给服务器, 服务器返回单一消息

(2) 添加多个个人信息  

注: 对应于Client streaming RPCs, 客户端使用提供的stream发送多个消息给服务端, 等客户端写完了所有的消息, 就会等待服务器读取这些消息, 然后返回响应消息. gRPC保证在一次RPC调用中, 消息是顺序的.

(3) 获取最多N个个人信息

注: 对应于Server streaming RPCs, 客户端发送一条消息给服务端, 然后获取一个stream来读取一系列的返回消息. 客户端会一直读取消息, 知道没有消息可读为止, gRPC保证在一次RPC调用中,消息是顺序的.

(4) 获取指定名字的所有个人信息

注: 对应于Bidirectional streaming RPCs, 这种rcp, 客户端和服务端通过一个read-write stream来发送一系列的消息. 这两个消息流可以独立操作, 就是说, 客户端和服务端可以以任意它们所想的顺序操作这两个消息流. 例如, 服务器可以等待接收到所有的客户端消息时,才开始向客户端发送消息, 或者它可以读一条消息, 然后给客户端发送一条消息, 或者别的想要的方式.  在两个消息流的其中一个中, 消息是顺序的.

 

在给出代码之前, 先说明一件事, 在grpc中, 请求参数和返回值类型都需要是message类型, 而不能是string, int32等类型.下面给出proto文件的定义:

// [START declaration]
syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";
// [END declaration]

// [START messages]
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;

    google.protobuf.Timestamp last_updated = 5;


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


// rpc调用的结果
message Result 
    bool success = 1;


// rpc请求的个数
message ReqNum 
    int32 num = 1;


message ReqName 
    string name = 1;


// [END messages]

// Interface exported by the server.
service Manage 
    // 添加一个人
    rpc AddPerson(Person) returns (Result) 
    // 添加很多人
    rpc AddPersons(stream Person) returns (Result) 
    // 获取指定数目的个人列表
    rpc GetPersonsLimit(ReqNum) returns (stream Person) 
    // 获取名字为输入的个人列表
    rpc GetPersons(stream ReqName) returns (stream Person) 

Person的定义和之前的protobuf中一致, 新加了一些用于grpc调用的结构体, 这些结构体很简单, 就不讲了. service Manage中定义的是这个服务提供的rpc调用接口.

(1) 添加一个个人信息 对应的是  AddPerson

(2) 添加多个个人信息 对应的是 AddPersons

(3) 获取最多N个个人信息 对应的是 GetPersonsLimit

(4) 获取指定名字的所有个人信息 对应的是 GetPersons

rpc定义很直观, 应该可以参照写出需要的rpc, 按照我了解的, 每个rpc有一个输入参数和一个输出参数, 这个需要注意.

下面给出服务端实现proto的Manage服务的代码:

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net"
	"sync"

	pb "personservice/tutorial"

	"google.golang.org/grpc"
)

// 个人信息服务端
type personServer struct 
	persons sync.Map


// AddPerson 添加一个个人信息
func (s *personServer) AddPerson(ctx context.Context, person *pb.Person) (*pb.Result, error) 
	s.persons.LoadOrStore(person.Name, person)
	return &pb.Result
		Success: true,
	, nil


// AddPersons 添加多个个人信息
func (s *personServer) AddPersons(stream pb.Manage_AddPersonsServer) error 
	for 
		person, err := stream.Recv()
		if err == io.EOF 
			return stream.SendAndClose(&pb.Result
				Success: true,
			)
		

		if err != nil 
			return err
		

		s.persons.LoadOrStore(person.Name, person)
	


// GetPersonsLimit 获取限定数目的个人信息
func (s *personServer) GetPersonsLimit(limitNum *pb.ReqNum, stream pb.Manage_GetPersonsLimitServer) error 
	var err error
	var i int32
	s.persons.Range(func(key, value interface) bool 
		person, ok := value.(*pb.Person)
		if !ok 
			return false
		
		err = stream.Send(person)
		if err != nil 
			return false
		
		i++
		if i >= (limitNum.Num) 
			return false
		
		return true
	)
	return err


// GetPersons 获取给定名字的所有个人信息
func (s *personServer) GetPersons(stream pb.Manage_GetPersonsServer) error 
	for 
		in, err := stream.Recv()
		if err == io.EOF 
			return nil
		
		if err != nil 
			return err
		
		value, ok := s.persons.Load(in.Name)
		if !ok 
			continue
		
		person, ok := value.(*pb.Person)
		if !ok 
			continue
		
		err = stream.Send(person)
		if err != nil 
			return err
		
	


func newServer() *personServer 
	s := &personServer
	return s


func main() 
	address := "localhost:50001"
	lis, err := net.Listen("tcp", address)
	if err != nil 
		log.Fatalf("failed to listen: %v", err)
	
	var opts []grpc.ServerOption
	grpcServer := grpc.NewServer(opts...)
	pb.RegisterManageServer(grpcServer, newServer())
	fmt.Println("Server listening on:", address)
	grpcServer.Serve(lis)

 下面代码实现了客户端对Manage服务的rpc调用:

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	pb "personservice/tutorial"
	"time"

	"google.golang.org/grpc"
)

const (
	rpcTimeOut = 10
)

// addPerson 用于添加个人信息
func addPerson(client pb.ManageClient, person *pb.Person) bool 
	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeOut*time.Second)
	defer cancel()
	res, err := client.AddPerson(ctx, person)
	if err != nil 
		log.Printf("client.AddPerson failed, error: %v\n", err)
		return false
	
	return res.Success



// addPersons 用来添加多个个人信息
func addPersons(client pb.ManageClient, persons []*pb.Person) bool 
	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeOut*time.Second)
	defer cancel()
	stream, err := client.AddPersons(ctx)
	if err != nil 
		log.Printf("client.AddPersons failed, error: %v\n", err)
		return false
	
	for _, person := range persons 
		if err := stream.Send(person); err != nil 
			log.Printf("stream.Send failed, error: %v\n", err)
			return false
		
	
	res, err := stream.CloseAndRecv()
	if err != nil 
		log.Printf("stream.CloseAndRecv failed, error: %v\n", err)
		return false
	
	return res.Success


// getPersonsLimit 用来获取指定数目的个人信息
func getPersonsLimit(client pb.ManageClient, limitNum int32) ([]*pb.Person, error) 
	var persons []*pb.Person
	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeOut*time.Second)
	defer cancel()
	num := pb.ReqNum
		Num: limitNum,
	
	stream, err := client.GetPersonsLimit(ctx, &num)
	if err != nil 
		log.Printf("client.GetPersonsLimit failed, error: %v\n", err)
		return persons, err
	
	for 
		person, err := stream.Recv()
		if err == io.EOF 
			break
		
		if err != nil 
			log.Printf("stream.Recv failed, error: %v\n", err)
			return persons, err
		
		persons = append(persons, person)
	

	return persons, nil


// getPersons 用来获取指定名字的所有个人信息
func getPersons(client pb.ManageClient, personNames []string) ([]*pb.Person, error) 
	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeOut*time.Second)
	defer cancel()
	stream, err := client.GetPersons(ctx)
	if err != nil 
		log.Printf("client.GetPersons failed, error: %v\n", err)
		return nil, err
	
	waitc := make(chan struct)
	// 发送个人名字信息
	go func() 
		for _, personName := range personNames 
			name := pb.ReqName
				Name: personName,
			
			if err := stream.Send(&name); err != nil 
				log.Printf("stream.Send failed, error: %v\n", err)
				break
			
		
		err := stream.CloseSend()
		if err != nil 
			log.Printf("stream.CloseSend failed, error: %v\n", err)
		
		close(waitc)
	()
	// 获取对应的所有个人信息
	var persons []*pb.Person
	var in *pb.Person
	for 
		in, err = stream.Recv()
		if err != nil 
			break
		
		persons = append(persons, in)
	

	<-waitc
	// 检查读取结果, err应该不会为nil
	if err == io.EOF || err == nil 
		return persons, nil
	
	log.Fatalf("stream.Recv failed, error: %v\n", err)
	return persons, err


func makePerson(name string, id int32, email string) pb.Person 
	return pb.Person
		Name:  name,
		Id:    id,
		Email: email,
	


func printPersons(persons []*pb.Person) 
	for _, person := range persons 
		fmt.Printf("%+v\n", person)
	
	fmt.Println("")


func main() 
	var opts []grpc.DialOption
	opts = append(opts, grpc.WithInsecure())
	conn, err := grpc.Dial("localhost:50001", opts...)
	if err != nil 
		log.Fatalf("grpc.Dial failed, error: %v\n", err)
	
	defer conn.Close()
	client := pb.NewManageClient(conn)

	person := makePerson("Tom", 1, "[email protected]")

	suc := addPerson(client, &person)
	if !suc 
		log.Fatalf("addPerson failed.\n")
	

	person = makePerson("Lilly", 2, "[email protected]")
	person2 := makePerson("Jim", 3, "[email protected]")

	persons := []*pb.Person&person, &person2
	suc = addPersons(client, persons)
	if !suc 
		log.Fatalf("addPersons failed.\n")
	

	resPersons, err := getPersonsLimit(client, 5)
	if err != nil 
		log.Fatalf("getPersonsLimit failed, error: %v\n", err)
	
	fmt.Println("getPersonsLimit output:")
	printPersons(resPersons)

	var personNames []string
	for _, person := range persons 
		personNames = append(personNames, person.GetName())
	
	resPersons, err = getPersons(client, personNames)
	if err != nil 
		log.Fatalf("getPersons failed, error: %v\n", err)
	
	fmt.Println("getPersons output:")
	printPersons(resPersons)

 

这个我没有使用单元测试, 可能使用单元测试会更好, 不过根据客户端代码和输出, 也可以验证服务的正确性.

完整的代码参考: https://github.com/ss-torres/personservice.git

如果有什么建议或者提议, 欢迎提出

golang之wait.until简单测试用例(代码片段)

测试目的,验证wait.Until的用法//测试wait.Until()的用途packagemainimport("fmt""k8s.io/apimachinery/pkg/util/wait""time")typestopstructfuncmain()stopCh:=make(chanstruct)//初始化一个计数器i:=0gowait.Until(func()fmt.Print 查看详情

3.微服务--grpc(代码片段)

...义方法参数和返回类型1.3安装gRPC和Protobuf?goget-u-vgithub.com/golang/protobuf/proto?gogetgoogle.golang.org/grpc(无法使用,用如下命令代替)?gitclonehttps://github.com/grpc/grpc-go.git$GOPATH/src/google.golang.org/grpc?gitclonehttps://github.com/golang/net.git$GOPATH/src/go... 查看详情

[go]grpc远程接口调用实现(代码片段)

...己实现//环境(将gopath/bin加入path)//安装grpc引擎goget-ugoogle.golang.org/grpc//安装grpc-go插件(适配go语言)goget-ugithub.com/golang/protobuf/protoc-gen-go//helloworld.proto//Therequestmessagecontainingtheuser'sname.messageHelloRequeststringname=1;//Theresponsemessagecontaining... 查看详情

go语言入门篇-grpc基于golang&java简单实现

....什么是RPC1.简介:RPC:RemoteProcedureCall,远程过程调用。简单来说就是两个进程之间的数据交互。正常服务端的接口服务是提供给用户端(在Web开发中就是浏览器)或者自身调用的,也就是本地过程调用。和本地过程调用相对的就是... 查看详情

golang中间件简单实现(代码片段)

golanghttp中间件源码链接golang的http中间件的实现首先实现一个http的handler接口typeHandlerinterfaceServeHTTP(ResponseWriter,*Request)typeRouterstructroutemap[string]Handlefunc(r*Router)ServeHTTP(whttp.ResponseWriter,req*h 查看详情

代码片段-golang实现简单的web服务器

------------------------------  下面一段代码,实现了最简单的Web服务器:编译环境:  LinuxMint18Cinnamon64-bit  Golang1.7------------------------------//main.gopackagemainimport( "fmt" "log" "net/http")//处理主页请求funcindex(whttp 查看详情

window下golang使用grpc入门案例(代码片段)

一、检查golang的安装环境https://golang.org/dl/需要墙,或者在这里下载https://pan.baidu.com/s/12tTmrVIel6sfeBInpt9lQA最新版本1.10下载msi安装即可goversion验证安装 查看详情

go语言实战(14)gin+grpc微服务实现备忘录(上)|用户模块(代码片段)

...github.com/CocaineCong/gRPC-todoList1.安装部分1.1安装gRPCgogetgoogle.golang.org/grpcgogetgoogle.golang.org/protobuf1.2安装protoc可用于通讯协议、数据存 查看详情

grpc以及grpc在go语言当中的应用(代码片段)

...现的业务逻辑2、安装依赖安装gRPC需要的依赖goget-ugoogle.golang.org/grpc3、安装插件安装定制给go和gRPC的插件goinstallgithub.com/golang/protobuf/protoc-gen-go@latestgoinstallgoogle.golang.org/grpc/cmd/protoc-gen-go-grpc@latest4、编写proto文件并且编译hell 查看详情

grpc以及grpc在go语言当中的应用(代码片段)

...现的业务逻辑2、安装依赖安装gRPC需要的依赖goget-ugoogle.golang.org/grpc3、安装插件安装定制给go和gRPC的插件goinstallgithub.com/golang/protobuf/protoc-gen-go@latestgoinstallgoogle.golang.org/grpc/cmd/protoc-gen-go-grpc@latest4、编写proto文件并且编译hell 查看详情

golang中设置函数默认参数的优雅实现(代码片段)

...那么,我们看下用FunctionalOptionsPatter的方式,我写了一个简单的例子。packagemainimport"fmt"//如何向func传递默认值typedialOptio 查看详情

grpc.v2(代码片段)

前述golanggrpc的API有2个版本:v1:github.com/golang/protobuf(proto+grpc)github.com/grpc/grpc-go(grpcstup)v2:google.golang.org/protobuf(proto)=>https://github.com/google/protobuf=>https://github.com/proto 查看详情

grpc:使用golang开发grpc服务端和client

...也须要使用protobuf的配置文件。可是golang以下的类库很的简单。并且golang的性能也很强悍呢。有些简单 查看详情

golang 语言grpc服务怎么同时支持grpc和http客户端调用?(代码片段)

大家好,我是frank。欢迎大家点击标题下方rpcCreateToDoList(ToDoListDetail)returns(CreateToDoListResult)rpcReadToDoList(ToDoListPage)returns(ReadToDoListByPage)...生成gRPC服务端存根使用protoc命令工具生成存根rpcCreateToDoList(ToDoListDeta 查看详情

golang实现的文件服务器(代码片段)

最近在学习golang,使用golang实现了一个最简单的文件服务器,程序只有简单的十多行代码,可以编译成windows,linux,mac多平台可执行文件。源码packagemainimport( "fmt" "net/http" "os" "path/filepath")funcmain( 查看详情

grpc:使用golang开发grpc服务端和客户端

...需要使用protobuf的配置文件。但是golang下面的类库非常的简单,而且golang的性能也很强悍呢。有些简单的业务逻辑 查看详情

golang实现简单rpc调用(代码片段)

RPC通信过程RPC的通信过程网上介绍很多,这里就不在单独介绍了,具体过程如下:1.Client以本地调用的方式发起调用;2.Clientstub收到调用后负责将被调用的方法名、参数等打包编码成特定格式成网络传输的消息体&#... 查看详情

golang实现简单rpc调用(代码片段)

RPC通信过程RPC的通信过程网上介绍很多,这里就不在单独介绍了,具体过程如下:1.Client以本地调用的方式发起调用;2.Clientstub收到调用后负责将被调用的方法名、参数等打包编码成特定格式成网络传输的消息体&#... 查看详情