概述
此篇中主要简单实现了,grpc的4种交互,包括普通请求,客户端流,服务端流,和双向流。其中服务端采用go语言实现,客户端将采用java实现。
在此篇中基本可以了解到protobuf的基本知识,以及多文件编译上的一些问题。以及在grpc请求上,对一些基本数据的发送和返回。其中你可以通过两种形式拿到自己想要的list数据。
需要注意的是此篇中用到的grpc为版本1.10。这个版本的context依然用的是golang.org/x/net/context中的,所以大家想用grpc一定要翻墙把这个下好。
首先我们先看一下proto文件。
此次练习实践,主要包含了两个proto文件,包含user_server.proto和user_vo.proto。一方面是对数据方法和服务方法进行分离,另一方面也是为了可以用到多文件编译。这样做还能在编译安卓用到的文件时遇到的不支持服务的问题(当然你也可以直接用java的生成代码)。
接下来让我们看一下, 在user_vo.proto中所定义的一些基本数据:
syntax = "proto3";
package testg;
message Empty{}
message UserList{
repeated User u = 1;
}
message User{
string user_id = 1;
string user_name = 2;
int32 age = 3;
Msg msg = 4;
}
message Msg{
string msg = 1;
}
首先头部的syntax="proto3"是必须的,否则他将按proto2编译。其次因为grpc无论是否有数据要发送返回,你都必须定义发送数据和返回数据。所以我们在这里定义了一个空的数据以方便我们在不需要传数据时使用。
protobuf在使用中只有基本类型,如果数据时一个list,你则需要用到repeatred这个关键字,他代表你的这个数据是不定的,可以是0个,1个或者更多。
oneof :可以让你包裹的几个字段只能有一个被赋值。
在proto2中的关键字,在proto3中已经删除:
required : 表示此字段必填。此字段需要小心设置因为如果忘记设置带有此关键字的字段,你的数据将被拒绝解析。
optional : 可选字段。(在proto3中默认就是可选的)
接下来让我们看看user_server.proto:
syntax = "proto3";
package testg;
import "testg/user_vo.proto";
service UserServer {
rpc GetUserById(User) returns (User);
rpc GetList(Empty) returns (UserList);
rpc GetListStream(Empty) returns (stream User);
rpc SetUserStream(stream User) returns (UserList);
rpc Chat(stream User) returns (stream User);
}
在这个文件中,主要是定义了grpc的服务和方法,其中我们通过使用import来引用我们要使用的其他文件的方法数据。在这里我们引用了user_vo.proto的数据类型。
数据和服务都定义好后,我们就可以开始编译了,在编译上,单文件和多文件编译时一样的,但是这里会有个小坑,如果不注意还是很头疼的。在编译一个文件时,我们基本不会遇到什么问题。但是多文件编译我们必须把所引用到的文件都一起编译,多个proto文件使用空格隔开。
如果我们一个一个编译的话,在go中你会遇到找不到某方法或数据的问题,而在java中会发现缺少某文件。
在此篇中go的编译为:
protoc --go_out=plugins=grpc:. ./user_vo.proto ./user_server.proto
在java的编译为:
1.通过 protoc --java_out=./ ./user_vo.proto ./user_server.proto生成proto文件的java版
2.通过 protoc --plugin=protoc-gen-grpc-java.exe --grpc-java_out=./ ./user_vo.proto ./user_server.proto生成grpc服务的和客户端文件
在多文件编译中,java版会额外生成一个UserServerOuterClass.java,即对某个文件的额外引用文件。如果一个一个编译你将会缺少这个文件。
所以大家在编译的时候一定要记住把所以引用到的proto文件都编译上,不要分开编译。
编译成功后,我们就可以开始代码的正式编写了。
作为服务端,我们首先需要实现所以 的接口。否则你将不能使用grpc服务。
其中具体要实现那些接口,大家可以在自己生成的grpc服务文件中找到,在此次中我们需要实现的接口有:
接下来就是定义一个空结构体来实现这五个方法:
func (u *UserServer) GetUserById(c context.Context,user *testg.User) (*testg.User, error) {
userId := user.UserId
if userId == "" {
return nil, nil
}
fmt.Println("userId:", userId)
ul := testListFunc()
for i:=0;i<len(ul.U);i++{
if ul.U[i].UserId == user.UserId {
return ul.U[i], nil
}
}
return nil, nil
}
这个方法通过客户端拿到传过来的参数,返回一个user的对象。
func (u *UserServer) GetList(c context.Context,e *testg.Empty) (*testg.UserList, error) {
return testListFunc(),nil
}
在这个方法中我们返回了list,这也是第一种返回list的方法,主要是利用了定义数据中的repeatred,让我们可以把user对象拿到一个list。
同时这两个方法都是对基础grpc服务的实现,即没有用到流,都是一问一答。在客户端发来请求后,服务端给客户端一个应答。
func (u *UserServer) GetListStream(e *testg.Empty, us testg.UserServer_GetListStreamServer) error {
ul := testListFunc()
for i:=0;i<len(ul.U);i++{
us.Send(ul.U[i])
}
return nil
}
这个方法中,我们实现了服务端流,即在客户端发来一个请求后,服务端以流的形式持续返回数据给客户端,通过方法看到,服务端流中有一个send的发送方法, 它允许我们不断给客户端发送数据。通过这个方法我们就实现第二种获得list的方法,每次 发送一个user对象,不间断发送直到没有数据可发。
func (u *UserServer) SetUserStream(us testg.UserServer_SetUserStreamServer) error {
for {
user,err := us.Recv()
if err == io.EOF {
return us.SendAndClose(testListFunc())
}
if err != nil {
return err
}
fmt.Println("user", user)
}
return nil
}
这个方法中我们实现了客户端流,即客户端会持续发送数据,当发送完成后,客户端会给服务端一个eof错误,服务端在拿到eof就关闭通道并发送返回数据给客户端。其中通过us.Recv()拿到客户端发来的数据。
func (u *UserServer) Chat(us testg.UserServer_ChatServer) error {
for {
user,err := us.Recv()
if err == io.EOF {
return nil
}
fmt.Println("user",user)
us.Send(user)
}
return nil
}
这个方法就是实现双向流的,我们通过Recv()方法拿到客户端数据,服务端通过Send()方法发送数据给客户端。这个基本就是客户端流和服务端流的结合。
在下一篇中,我们将讲关于客户端的基本实现。
大家也可以去GitHub下载我的练习源码:
https://github.com/aixinaxc/grpcserver (服务端)
https://github.com/aixinaxc/grpcclient-golang (客户端)
最后
以上就是坦率西牛为你收集整理的grpc基本实践(一)的全部内容,希望文章能够帮你解决grpc基本实践(一)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复