公司要把产品转架构,虽然和我们驱动开发没什么关系,但还是抱着看热闹的心态研究了下和架构相关的一些问题。这阶段主要研究了下RPC这个东西。
RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
说到底就是在调用函数的时候从
call far ptr 目标地址
变为
call ip:port:service:procedure
表现上看和正常函数一样,当已经不是用本地资源了,而是用来远程的资源,这种机制正合分布式的思想。
一般RPC分为两个步骤,一个是序列化和反序列化,另一个是网络通信,这如果想要高效的实现RPC,这两部分都要足够高效,而google正式这样的效率狂人。去年google发布了自己的RPC框架grpc,但一方面grpc环境在windows中极难搭建,另一方面grpc在网络通信这块是基于HTTP2的,效率总归不如私有协议来的快,于是就想着自己动手丰衣足食,自己动手做一个rpc。现在基于protobuf的rpc都是结合libevent实现的,所以想试试使用libuv。在序列化/反序列化和网络通信这块分别用了google的两个开源项目:protobuf和libuv,protobuf是单独的项目,是序列化和反序列化项目,libuv则是集成在nodejs中的,是用CompleteIO实现的高效IO库。代码均下载自github上。
由于工作原因平时习惯了用C语言,所以在protobuf上我们选择使用他的兄弟工程protobuf-c,这个工程在idl的编译上使用了protobuf,而序列化和反序列化则使用了c代码来实现。libuv因为他的异步调用机制和单线程阻塞的工作方式,使得他作为服务器端是异常高效稳定的,但也这是这个特性使得它不太适合用于开发客户端。
先说下思路:客户端首先调用了一个函数,这个调用转换为一个网络请求并提供了一个回调函数,等请求返回时将调用这个回调函数,当这个请求发送到网络后,服务端解包,找到对应服务的例程并调用例程,得到返回值后,将返回值组包发送给客户端,客户端解包后调用刚刚的回调函数。
首先来设计私有协议,协议中需要确定网络包是请求包还是响应包,我们直接在数据包头两个字节设置特征码来区别类型:
#define REQUEST_MAGIC "\x90\x90"
#define RESPONSE_MAGIC "\x91\x91"
#define MAGIC_SIZE 2
#define TYPE_UNKNOWN 0
#define TYPE_REQUEST 1
#define TYPE_RESPONSE 2
接下来是头的设计,protobuf中一个服务是用一个service来修饰,而服务中的例程则是一系列类似函数的声明。类似于这样
service RpcTest{
rpc GetTest(Test1) returns(Test2) {}
}
于是便可以以一个服务名和一个例程索引来定位一个具体的例程,同时还需要有一个标识来确定一个响应对应的是哪个请求,所以请求头可以这样设计:
message RpcRequest{
required string service = 2;
required int32 request = 3;
required int32 id = 4;
}
而响应头只需要告诉客户端这个响应的是哪个请求:
message RpcResponse{
required int32 id = 2;
}