【注意】最后更新于 November 22, 2017,文中内容可能已过时,请谨慎使用。
在学习区块链的途中,看到了grpc。很久之前就听说过rpc,这次就仔细来一探究竟。
综述
rpc--Remote Procedure Call,远程过程调用,就是这边一个程序,调用另一个程序的函数,完成一个程序的调用。
这个协议底层的协议不一样,不过grpc使用的是http2。
我个人对grpc的理解有点简单,也不知道准不准确。
>如果不用rpc,我们也可以在服务端创建一个http服务器,接受收到的数据内容,然后解析出来要使用哪个函数,把需要的参数传递进去,执行出来得到一个结果,作为一个http相应传递回去。这样就完成了一个rpc
当然了,grpc所做的肯定要比这个复杂,它把这些底层的http全部隐藏起来,客户端调用函数名,服务器端实现函数,就完成了这个操作。对于我们开发者来说,确实简单多了。
废话不多说,我们来具体的看一看吧。
创建
进行通信就必须得有一个双方共同遵守的标准,这我们就使用proto进行通信。当然了,我们也可以使用json等数据格式。都是无所谓的。
数据格式
为了完成基础的通信内容,我们定义了以下几个基础的数据格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
message PersonReq{
int32 id=1;
}
message Person{
int32 id=1;
int32 manage=2;
string name=3;
bool gender=4;
string mail=5;
string address=6;
string phone=7;
}
message PersonResp{
Person person=1;
}
message Company{
int32 id=1;
string company_name=2;
repeated Person mgr=3;
repeated Person stuff=4;
}
|
(又被我给写成了http风格)
内容很简单,我直接写一个int32不能传递,只好做了个req类型,为了统一,我就又写了一个resp。然后做了一个公司的结构,包含管理层和职员。
这就定义好了基础的数据格式,还得需要把用户能调用的程序写出来啊。怎么办?
proto给我们定义了一个结构:service
,可以制定出对应的服务。
好的,那我们就来吧。
1
2
3
4
5
|
service PersonMgr{
rpc GetPerson(PersonReq) returns (PersonResp) {}
rpc AddPerson(stream Person) returns (stream Company) {}
}
|
估计都是一看就懂了,没有什么复杂的,不过需要注意的是service的名称,后面创建的client以及server都跟这个名字相关。
如果单次调用的话就可以直接传递对应的参数就可以,需要进行流的数据传输的话,用stream就好。
我们接下来得生成对应的go文件,执行这条指令即可
1
|
protoc --go_out=plugins=grpc:. message.proto
|
就生成了对应的go文件。我们再来看下。
我们主要关注对于使用的地方,其他的内容就不做过多考虑了。
client端
在client端,生成的文件有这个结构体和生成函数
1
2
3
4
5
6
7
|
type personMgrClient struct {
cc *grpc.ClientConn
}
func NewPersonMgrClient(cc *grpc.ClientConn) PersonMgrClient {
return &personMgrClient{cc}
}
|
我们在创建客户端的时候只需要导入连接,就可以获得对应的操作句柄。
然后有这么一个接口定义
1
2
3
4
5
|
type PersonMgrClient interface {
GetPerson(ctx context.Context, in *PersonReq, opts ...grpc.CallOption) (*PersonResp, error)
AddPerson(ctx context.Context, opts ...grpc.CallOption) (PersonMgr_AddPersonClient, error)
GetCompany(ctx context.Context, in *CompanyReq, opts ...grpc.CallOption) (*CompanyResp, error)
}
|
下面针对personMgr这个结构体都做了具体的实现。所以我们直接在客户端调用即可。
server端
server这一段要稍微显得复杂一点,因为它不单单是对一个进行操作。
1
2
3
4
5
6
7
8
9
|
type PersonMgrServer interface {
GetPerson(context.Context, *PersonReq) (*PersonResp, error)
AddPerson(PersonMgr_AddPersonServer) error
GetCompany(context.Context, *CompanyReq) (*CompanyResp, error)
}
func RegisterPersonMgrServer(s *grpc.Server, srv PersonMgrServer) {
s.RegisterService(&_PersonMgr_serviceDesc, srv)
}
|
服务器端为了使用需要使用注册函数,而不是像客户端那样使用new。除此之外,服务器端还需要对这几个函数实现继承。写出具体的操作方法。
应用
经过了对源结构以及生成文件的分析,我们就可以书写自己的程序了。
服务端代码
我们首先来分析服务端代码,这个显得复杂点,而且这个明白了之后,客户端手到擒来。
主函数不复杂,就这么几句话
1
2
3
4
5
6
7
8
9
|
lis, err := net.Listen("tcp", "localhost:8080")
if err != nil {
fmt.Println("failed to listen to localhost:8080")
}
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
pb.RegisterPersonMgrServer(grpcServer, newServer())
fmt.Println("server start")
grpcServer.Serve(lis)
|
就是开启一个tcp监听端口,写一个结构体,实现我们上面说的需要实现的那几个方法,然后把这个tcp服务添加给grpc服务就好了。
这个是我们实现的结构体。
1
2
3
4
5
6
7
8
9
|
type personServer struct {
persons []*pb.Person
companies []*pb.Company
}
func newServer() *personServer {
s := new(personServer)
......
return s
}
|
就是记录了几条数据,初始化的时候生成几条数据而已。
再然后就是接口的实现
1
2
|
func (p *personServer) GetPerson(ctx context.Context, req *pb.PersonReq) (*pb.PersonResp, error)
func (p *personServer) AddPerson(stream pb.PersonMgr_AddPersonServer) error
|
没什么太难的。
这个地方稍微吐槽下,go这种写法太奇怪,oo不像,op也不是,太奇葩。
客户端代码
客户端这块代码更简单了,就是创建一个连接,直接调用函数就好啦。
1
2
3
4
5
6
7
8
|
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
conn, err := grpc.Dial("localhost:8080", opts...)
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
client := pb.NewPersonMgrClient(conn)
|
后面需要什么操作就是直接client.XXX()
就好了,很简单。
小结
这些也算是大体看完了grpc。不得不说,google就是大佬,做了好多好东西啊!