yangyf83@amail2.sysu.edu.cn
在课程“计算机网络” 中的大作业——尝试使用 C++ 和 protobuf 完成一个简单的玩具级的 RPC 实现。
- 整体采用 http 协议作为传输的应用层协议
- 使用 protobuf 作为序列化和反序列化的工具
- 手撸了一份 C++ Linux Http 协议栈,支持同步,异步,并发调用
- 保留了超时控制的接口
- 可以在用户端的服务发现和负载均衡
- 客户端的同步和异步调用
- 服务端使用IO多路复用进行网络IO
项目使用谷歌 protobuf 进行序列化和反序列化,在 rpc 内核中,我们定义在对请求和回应进行动态代理之后的同一消息类型如下:
message RpcMessage
{
MsgType type = 1; //消息类型
int64 id = 2; //请求id
uint32 serviceId = 3; //服务ID
uint32 methodId = 4; //方法ID
bytes request = 5; //请求 protocol二进制编码
bytes response = 6; //响应 protocol二进制编码
ErrorCode error = 7; //错误码
}
其中有
enum MsgType
{
REQUEST = 0;
RESPONSE = 1;
ERROR = 2;
}
enum ErrorCode
{
NO_ERROR = 0; //正确响应
WRONG_PROTO = 1; //协议错误
NO_SERVICE = 2; //找不到服务
NO_METHOD = 3; //找不到方法
INVALID_REQUEST = 4; //错误请求
INVALID_RESPONSE = 5; //错误响应
TIMEOUT = 6; //请求超时
PENDDING_LIMIT = 7; //达到最大pending请求限制
}
分别表示消息的类型和错误码。详细的序列化和反序列化过程,我们在动态代理中进行说明。
服务注册在 net/RpcServer.cpp
中实现。
在服务定义完成之后,我们对于服务名和每一个方法名都进行哈希计算,得到对应的id,定义一个哈希表来存储服务中的方法id和具体方法之间的关系。
对于客户端,客户端在收到一个请求的时候,首先会对这个请求进行封装——收到的已经由 protobuf 封装之后的二进制请求信息作为对应的 REQUEST
,生产出一个新的 RpcMessage
类型的代理消息序列化之后发送给客户端进行处理,在收到客户端对应的response之后,代理类又进行一次反序列化,拿到其中的 RESPONSE
传给调用者,这样在调用者不可感的情况下完成了客户端的动态代理。
对于服务端:当收到一个 protobuf 类型的请求的时候,Server首先得到其对应的服务和请求id和对应的 REQUEST 信息,找到对应的方法,反序列化 REQUEST 得到函数调用的参数,使用 handleRpcCall()
函数进行方法调用拿到对应的 response 消息之后作为对应 RpcMessage
消息的 RESPONSE
产生新的代理后的message序列化后进行网络传输,这样也就完成了服务端的动态代理。
得益于 protobuf 提供的框架,客户端同步和异步的实现相对来说比较容易。在net/RpcClient.hpp
中,我们定义了客户端调用类 RpcChannel
,在类中我们重写了google::protobuf::RpcChannel::CallMethod()
函数,通过查阅 protobuf 文档我们直到函数的最后一个参数 google::protobuf::Closure *done
是指异步调用对应的回调函数指针,我们只需要判断 done
是否为空就能判断是否是异步调用,进行相应的操作即可。在异步调用结束之后运行对应的回调函数。
网络通信部分使用 HTTP 协议进行,底层为TCP/IP协议。
由于C++原生的网络库基本上也只提供操作系统级别的抽象,这也决定了我的项目只能运行在 Linux 上,对其他平台无能为力。后面我会考虑换用folly, wangle等第三方库,网络部分可能会变得更普适一些。
我们使用一致性哈希算法来进行负载均衡的处理。服务器信息储存在跳表 SkipList 中。相关代码在文件夹 util
下。
在本项目中,这一部分内容是RPC框架提供抽象和底层调用支持,具体实现需要用户进行。在 example/RegisterCenter.cpp
中,我制作了一个最简单的抽象模板,可以提供最基础的服务注册功能。
运行一个简单的同步调用demo,可以看到可以正常进行远程过程调用。
在本次实验中,我使用 Protobuf 进行了一个简单的C++远程过程调用实现,作为一个脚手架完成了RPC所需要的各项基本功能,也给很多可以实现的功能比如超时控制,浏览器兼容等等留下了兼容和升级的空间。
在本学期的学习中,我也从无到有的对于计算机网络有了基础的了解和认识,对于计算机网络的原理、发展和应用都进行了不同程度的学习,在实践中也提升了自己的实践水平和应用能力,在本课程中收获良多。