小小千想和您聊一聊

当前位置: 首页> 技术分享> gRPC入门和使用

gRPC入门和使用

gRPC简介

gRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。gRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好。本节将讲述gRPC的简单用法。

gRPC框架技术栈

Go语言的gRPC技术栈如图4-1所示:

说明:

  • 1、最底层为TCP或Unix Socket协议。

  • 2、第二层是HTTP/2协议的实现。

  • 3、针对Go语言的gRPC核心库。

  • 4、应用程序通过gRPC插件生产的Stub代码和gRPC核心库通信,也可以直接和gRPC核心库通信。

gRPC的安装

  • 命令安装通过如下命令可以安装go版本的gRPC框架:

go get -u google.golang.org/grpc

通过go的get命令可以安装完成。

  • 下载安装如果安装失败,也可以自行到github网站下载grpc-go代码库,然后解压,放入到当前系统的$GOPATH/src中,创建google.golang.org目录,将解压后的文件放入到新创建的目录中,修改解压后的目录名称为grpc,

gRPC使用方法

1、准备.proto文件

创建person.proto文件,定义HelloService接口:


syntax = "proto3";
package person;
message Person {
    string name = 1;
    int32 age = 2;
}

service HelloService{
    rpc Hello (Person) returns (Person);
}
2、使用gRPC插件生成gRPC代码:
 $ protoc --go_out=plugins=grpc:. person.proto

gRPC插件会为服务端和客户端生成不同的接口:


type HelloServiceServer interface {
	Hello(context.Context, *Person) (*Person, error)
}

type HelloServiceClient interface {
	Hello(ctx context.Context, in *Person, opts ...grpc.CallOption) (*Person, error)
}

gRPC通过context.Context参数,为每个方法调用提供了上下文支持。客户端在调用方法的时候,可以通过可选的grpc.CallOption类型的参数提供额外的上下文信息。

3、基于HelloServiceServe接口实现具体服务

基于服务端的HelloServiceServer接口可以重新实现HelloService服务:


type HelloService struct {
}

func (hello *HelloService) Hello(ctx context.Context, args *person.Person) (*person.Person, error) {
	reply := &person.Person{Name: "hello:" + args.GetName()}
	return reply, nil
}
4、gRPC服务启动流程

在服务端的main中,执行启动服务和监听程序,如下所示:


func main() {
	grpcServer := grpc.NewServer()
    person.RegisterHelloServiceServer(grpcServer, new(HelloService))
	lis, err := net.Listen("tcp", ":1234")
	if err != nil {
		log.Fatal(err)
	}
	grpcServer.Serve(lis)
}

首先是通过grpc.NewServer()构造一个gRPC服务对象,然后通过gRPC插件生成的RegisterHelloServiceServer函数注册我们实现的HelloServiceImpl服务。然后通过grpcServer.Serve(lis)在一个监听端口上提供gRPC服务。

5、客户端链接gRPC服务

在客户端中使用grpc连接rpc服务的实现也很简单:


func main() {

	//1、Dail连接
	conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	client := person.NewHelloServiceClient(conn)
	per := &person.Person{Name: "Davie", Age: 18}
	response, err := client.Hello(context.Background(), per)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(response.GetName(), response.GetAge())
}

其中grpc.Dial负责和gRPC服务建立链接,然后NewHelloServiceClient函数基于已经建立的链接构造HelloServiceClient对象。返回的client其实是一个HelloServiceClient接口对象,通过接口定义的方法就可以调用服务端对应的gRPC服务提供的方法。

gRPC和标准的RPC的区别

gRPC和标准库的RPC框架有一个区别,gRPC生成的接口并不支持异步调用。不过我们可以在多个Goroutine之间安全地共享gRPC底层的HTTP/2链接,因此可以通过在另一个Goroutine阻塞调用的方式模拟异步调用。

gRPC流

RPC是远程函数调用,因此每次调用的函数参数和返回值不能太大,否则将严重影响每次调用的响应时间。因此传统的RPC方法调用对于上传和下载较大数据量场景并不适合。同时传统RPC模式也不适用于对时间不确定的订阅和发布模式。为此,gRPC框架针对服务器端和客户端分别提供了流特性。

服务端或客户端的单向流是双向流的特例,我们在HelloService增加一个支持双向流的Channel方法:

 rpc Channel (stream Person) returns (stream Person);

关键字stream指定启用流特性,参数部分是接收客户端参数的流,返回值是返回给客户端的流。

1、 重新编译person.proto文件

通过如下命令重新编译:

 protoc --go_out=plugins=grpc:. person.proto

重新生成代码可以看到接口中新增加的Channel方法的定义:


type HelloServiceServer interface {
	Hello(context.Context, *Person) (*Person, error)
	Channel(HelloService_ChannelServer) error
}

type HelloServiceClient interface {
	Hello(ctx context.Context, in *Person, opts ...grpc.CallOption) (*Person, error)
	Channel(ctx context.Context, opts ...grpc.CallOption) (HelloService_ChannelClient, error)
}
2、生成新的方法参数

在服务端的Channel方法参数是一个新的HelloService_ChannelServer类型的参数,可以用于和客户端双向通信。客户端的Channel方法返回一个HelloService_ChannelClient类型的返回值,可以用于和服务端进行双向通信。

HelloService_ChannelServer和HelloService_ChannelClient均为接口类型:


type HelloService_ChannelServer interface {
	Send(*Person) error
	Recv() (*Person, error)
	grpc.ServerStream
}

type HelloService_ChannelClient interface {
	Send(*Person) error
	Recv() (*Person, error)
	grpc.ClientStream
}
3、服务端实现流接口方法实现

通过以上代码,我们可以发现服务端和客户端的流辅助接口均定义了Send和Recv方法用于流数据的双向通信。


func (hello *HelloService) Channel(stream person.HelloService_ChannelServer) error {
	for {
		args, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
		response := &person.Person{Name: "姓名:" + args.GetName(), Age: args.GetAge()}
		err = stream.Send(response)
		if err != nil {
			return nil
		}
	}
}

服务端在循环中接收客户端发来的数据,如果遇到io.EOF表示客户端流被关闭,如果函数退出表示服务端流关闭。生成返回的数据通过流发送给客户端,双向流数据的发送和接收都是完全独立的行为。需要注意的是,发送和接收的操作并不需要一一对应,用户可以根据真实场景进行组织代码。

4、客户端实现流方法的调用

客户端需要先调用Channel方法获取返回的流对象:


stream, err := client.Channel(context.Background())
	if err != nil {
		log.Fatal(err)
	}

将发送和接收操作放到两个独立的Goroutine。向服务端发送数据和接受数据代码实现如下:


//向服务端发送数据
	go func() {
		for {
			if err := stream.Send(&person.Person{Name: "Davie", Age: 18}); err != nil {
				log.Fatal(err)
			}
			time.Sleep(time.Second)
		}
	}()

	//循环中接收服务端返回的数据
	for {
		response, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				break
			}
			log.Fatal(err)
		}
		fmt.Println(response.GetName())
	}

通过该案例和代码,我们演示了gRPC框架的使用流程,以及gRPC框架流的使用。


上一篇:RPC与Protobuf

下一篇:gRPC安全认证和拦截器

QQ技术交流群

千锋go语言官方①群
816085262

加入群聊

用户登录

手机号:

密码:

图形验证码:

点击切换

用户注册

手机号:

登录密码:

图形验证码:

点击切换

短信验证码:

获取验证码

忘记密码

1安全验证

2重置密码

手机号:

图形验证码:

短信验证码:

获取验证码

忘记密码

1安全验证

2重置密码

新密码:

确认新密码:

获取课程

添加小千老师微信,获取课程信息

如何获取课程?

一、需拥有此本教材

如没有,可点击下方入口购买当当购买入口京东购买入口

二、添加小千老师,发送拥有凭证,解锁课程资源

1.购买该教材的订单信息
2.拥有的实体书信息等

更换手机号

新手机号:

图形验证码:

短信验证码:

获取验证码