前言
关于buttonrpc.hpp的解析
server
我们从server.cpp的main函数部分来学习这个hpp文件
int main() {
buttonrpc server;
server.as_server(5555);
//server.bind("redis_command", redis_command);
RedisServer::getInstance()->start();
server.bind("redis_command", &RedisServer::handleClient, RedisServer::getInstance());
// std::cout << "run rpc server on: " << 5555 << std::endl;
server.run();
RedisServer::getInstance()->start();
}
RedisServer::getInstance()->start();
顾名思义,声明一个redis实例并启动,这部分不在本文的讨论内容之中。
因此我们先从as_sever,bind,run这三个函数入手,逐步解析butonrpc。
as_server
void buttonrpc::as_server( int port )
{
m_role = RPC_SERVER; //设置角色为服务器
m_socket = new zmq::socket_t(m_context, ZMQ_REP); //创建一个套接字 参数为上下文和套接字类型 //ZMQ_REP 用于请求-应答模式
ostringstream os;
os << "tcp://*:" << port;
m_socket->bind (os.str()); //绑定到指定的地址
}
as_server是初始化一个服务器,将buttonrpc作为的角色设置为服务器。其主体过程就是对zeromq对使用,因此在这之前需要对zeromq有基础的理解。关于zeromq,其链接放在参考部分。
bind
有两个bind函数,根据参数我们很容易得知调用的bind是下面这个函数
template<typename F, typename S>
inline void buttonrpc::bind(std::string name, F func, S* s) //类函数
{
m_handlers[name] = std::bind(&buttonrpc::callproxy<F, S>, this, func, s, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
}
他使用std:bind将函数存进函数映射表。我们再查看一下m_handlers的定义,如下:
std::map<std::string, std::function<void(Serializer*, const char*, int)>> m_handlers; //函数映射表
可以看到定义的key类型为string,而value类型是std:function。
std::function是C++11引入的标准库组件,位于<functional>头文件中。它的主要作用是将可调用对象封装为一个函数对象,提供一种统一的方式来处理各种类型的可调用对象。
那么它是如何与bind对应的呢?
在std:bind中,它将buttonrpc:callproxy<F,S>绑定在this指针,而后续的func,s就是callproxy函数的前两个参数,在这里表示这两个参数的值固定。而后面的三个占位符表示还有三个不固定的参数。这样我们很容易就可以对应上。三个占位符与std:function的三个参数对应。
即Serializer*, const char*, int。
我们跳转到callproxy函数。
callproxy
template<typename F, typename S>
inline void buttonrpc::callproxy(F fun, S * s, Serializer * pr, const char * data, int len)//代理类函数
{
callproxy_(fun, s, pr, data, len);
}
发现第一个参数F fun和第二个参数S *s对应bind的fun和s,那么后三个参数Serializer * pr, const char * data, int len,在std:bind中是三个占位符,并且和m_handlers的std:function中的三个参数完美对应。
因此我们可以了解到,在使用m_handlers时,根据name调用对应的代理函数。
callproxy_
template<typename R, typename C, typename S>
void callproxy_(R(C::* func)(), S* s, Serializer* pr, const char* data, int len) {
callproxy_(std::function<R()>(std::bind(func, s)), pr, data, len);
}
这里利用了std:function函数将func和s绑定到了一起并作为一个新的func传递给另外一个参数不同的callproxy_,我们跟着查看这个函数:
template<typename R>
void buttonrpc::callproxy_(std::function<R()> func, Serializer* pr, const char* data, int len)
{
typename type_xx<R>::type r = call_helper<R>(std::bind(func));
value_t<R> val;
val.set_code(RPC_ERR_SUCCESS);
val.set_val(r);
(*pr) << val;
}
调用了call_helper,而call_helper的内容就是对f函数进行调用,即调用具体的对应的函数。而根据返回值也设定了返回0或者返回f函数的返回值,对应不同的处理。
文件还实现了从无参到五个参数的函数代理使用,其区别就在与参数个数和有参数时需要对参数进行序列化传递。
run
void buttonrpc::run()
{
if (m_role != RPC_SERVER) { //如果不是服务器
return;
}
while (1){
zmq::message_t data; //创建一个消息
recv(data); //接收数据 没消息就阻塞
StreamBuffer iodev((char*)data.data(), data.size());//创建一个流缓冲区
Serializer ds(iodev); //创建一个序列化器
std::string funname;
ds >> funname; //读取函数名
Serializer* r = call_(funname, ds.current(), ds.size()- funname.size()); //调用函数
zmq::message_t retmsg (r->size()); //创建一个消息
memcpy (retmsg.data (), r->data(), r->size()); //拷贝数据
send(retmsg); //发送数据
delete r;
}
}
注释基本将函数解释完毕。这里我们思考一个redis命令的使用,例如set key value。那么从data中读取到的funname就是set。因此call传递的就是set,缓冲区当前字节流的位置current,以及参数的长度ds.size()- funname.size()。
call_
Serializer* buttonrpc::call_(std::string name, const char* data, int len)
{
Serializer* ds = new Serializer(); //创建一个序列化器
if (m_handlers.find(name) == m_handlers.end()) { //如果没有找到函数
(*ds) << value_t<int>::code_type(RPC_ERR_FUNCTIION_NOT_BIND); //设置错误码
(*ds) << value_t<int>::msg_type("function not bind: " + name); //设置错误信息
return ds;
}
auto fun = m_handlers[name]; //获取函数
fun(ds, data, len); //调用函数
ds->reset(); //重置序列号容器
return ds;
}
很明显,因为我们之前存储了对应的函数在m_handlers中,这里可以根据name去查找对应的函数。如果查找到就直接进行函数调用即可。
总结
std:bind和std:function的结合使用,让对这两个c++11特性不熟悉的人会感觉代码理解很困难。但在了解这两个标准组件之后,就比较简单了。
ps:理解简单,让我来写这样的代码感觉就很困难了。