在学习区块链的途中,看到了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就是大佬,做了好多好东西啊!