【C++】Boost.Asio+Serialization 建立RPC demo(服务端)

接上篇blog,RPC服务端主要设计两个主要内容:

  1. 调用函数进行统一绑定。
  2. 调用函数参数展开。
  3. 统一接口的调用。
    这里我不考虑类成员函数绑定,远程调用不好设计。
接收数据

服务端能接收的数据存放在char* readBuffer_里面,Boost.Serialization的binary_oarchive对象来恢复传递的函数名字符串、函数参数。服务端根据传输的函数名来调用相应的函数并输入函数参数。

设计绑定函数

如果是原始做法,那么就是:

if (fname == "Func1")
	func1(args...);
if (fname == "Func2")
	func2(args...);

这种做法其实也可以,就是可扩展性差了些,需要大量改源码,而且不能对RPC方法进行管理,比如某些客户端不能用某些方法之类的。
我希望将所有RPC方法统一汇总到一个std::map< std::string, Func > handlers_中,这样可以直接handlers_[fname][ fname ]( args )调用方法。统一调用最大的困难在于,不同函数、仿函数、成员函数的返回值、参数类型、参数数量都是不定的,一个typename Func就不能用。下面的做法可以转换到统一的std::function< void(boost::archive::binary_oarchive*,
boost::archive::binary_iarchive*) >类型,同时又封装了不同的Func:

std::map<std::string, std::function<void(boost::archive::binary_oarchive*,
		boost::archive::binary_iarchive*)> > handlers_;

template<typename F>
void CallProxy(F f, boost::archive::binary_oarchive *oa,
	boost::archive::binary_iarchive *ia) {
	CallProxy_(f, oa, ia);
}

template<typename F>
void Bind(std::string fname, F f) {
	handlers_[fname] = std::bind(&CallProxy<F>, f, std::placeholders::_1,
		std::placeholders::_2);
}

让人惊讶,通过std::bind获得一个std::function对象,本应不同的函数模板typename F被去掉了,然后就能放入一个std::map了!

获取所有参数

所有的数据均通过asio传输保存在char *readBuffer_中,需要通过Boost.Serialization的binary_iarchive来获取。困难在于,不同的函数类型有不同数量、不同类型的参数,如何获取是一个麻烦。虽然依然可以通过if else if …的方式来定制,同上面所述,工作量大,不易管理。类比上一篇blog的描述,我们可以通过展开可变模板的方法来自动获取所有参数。

template<typename Tuple, std::size_t... I>
Tuple GetTuple(boost::archive::binary_iarchive *ia, std::index_sequence<I...>) {
	Tuple t;
	std::initializer_list<int>{ ((*ia >> std::get<I>(t)), 0)... };
	return t;
}

template<typename R, typename... Params>

constexpr auto N = sizeof...(Params);
std::tuple<typename std::decay<Params>::type...> args = GetTuple(oa, std::make_index_sequence<N>());

通过sizeof,(Params)能获取模板数量,但是直接获取的数值是不能作为模板的,因此要用constexpr来修饰获取的模板数量。这里可以看出,与const相比,const修饰一个不可变的变量(常量定义1);constexpr修饰一个编译期可求值的常量(常量定义2),类似于#define N sizeof…(Params)。
上面获得的模板数量N可以作为模板,放入std::make_index_sequence()得到一个数值序列模板,就可以像上一篇blog所述,用std::initializer_list进行可变模板展开,然后用ia反序列化数据。

统一函数调用

上一节遗留一个问题,Params参数模板列表如何获取。这一节的问题,如果调用不同函数模板的RPC方法。这两个问题将通过模板技术统一解决。
先来看实际功能函数CallProxy_的定义:

template<typename R, typename... Params>
void CallProxy_(std::function<R(Params... ps)> f,
	boost::archive::binary_oarchive *oa,
	boost::archive::binary_iarchive *ia) {

	constexpr auto N = sizeof...(Params);//std::tuple_size<typename std::decay<Params>::type...>::value;
	auto args = GetTuple<std::tuple<typename std::decay<Params>::type...> >(ia, std::make_index_sequence<N>());

	typename type_transfer<R>::type ret = CallHelper<R>(f, args);
	Respond<R> res;
	res.errorCode_ = 1;
	res.val_ = ret;
	*oa << res;
}

template<typename R, typename... Params>
void CallProxy_(R(*f)(Params...), 
	boost::archive::binary_oarchive *oa,
	boost::archive::binary_iarchive *ia) {

	CallProxy_(std::function<R(Params...)>(f), oa, ia);
}

template<typename F>
void CallProxy(F f, boost::archive::binary_oarchive *oa,
	boost::archive::binary_iarchive *ia) {
	CallProxy_(f, oa, ia);
}

同样让人惊讶,CallProxy中还只是typename F的函数模板,实例化后可以解析为两个模板(不考虑类成员函数):R(*f)(Params…)和std::function<R(Params… ps)> f。这样返回值被拆开为R,参数列表被拆为Params…。binary_iarchive对象ia和Params…一起,通过上篇blog介绍的方法进行展开和反序列化,不过为了统一获取参数,反序列化的参数都存放在一个std::tuple中;这间接说明这种RPC方法不适合把大量数据作为参数。
在CallProxy_函数中,返回值直接通过binary_oarchive写入到了char * writeBuffer中,因此调用完CallProxy函数之后,直接发送writeBuffer数据即可向客户端传输结果。
接下来是调用实际的函数CallHelper:

template<typename Func,typename Tuple, std::size_t... Index >
auto invoke(Func &&f, Tuple &&t, std::index_sequence<Index...>) {
	return f(std::get<Index>(std::forward<Tuple>(t))...);
}

template<typename R, typename F, typename ArgsTuple>
typename std::enable_if<std::is_same<R, void>::value, typename type_transfer<R>::type>::type
CallHelper(F f, ArgsTuple args) {
	constexpr auto size = std::tuple_size<typename std::decay<ArgsTuple>::type>::value;
	invoke(f, args, std::make_index_sequence<size>{});
	return 0;
}

template<typename R, typename F, typename ArgsTuple>
typename std::enable_if<!std::is_same<R, void>::value, typename type_transfer<R>::type>::type
CallHelper(F f, ArgsTuple args) {
	constexpr auto size = std::tuple_size<typename std::decay<ArgsTuple>::type>::value;
	return invoke(f, args, std::make_index_sequence<size>{});
}

同样的,用constexpr拿到一个可作为模板参数的模板数量;std::tuple_size用于获取std::tuple的参数数量。std::enable_if用于进行模板特化,这里主要是处理void的特殊情况。std::is_same判断两个模板是否相同,返回true和false。std::enable_if< condition, typename T >::type,当condition是true的时候,模板就是T。当模板R是void时,我们要专门返回一个0值。
在Invoke输入的参数中包含一个std::make_index_sequence< size >,这样就建立模板数值序列,在invoke中进行参数解包,依次解析为真实RPC方法的函数参数。
这里有一个有趣的东西,就是invoke函数,即使typename Func f的返回值是void,invoke依然能return f(args);但是这一次返回后,不能使用auto ret = invoke(f, args),因为return没返回值。

服务端核心代码:
class Server : public std::enable_shared_from_this<Server>,
	public boost::noncopyable {
public:
	Server(boost::asio::io_service &ser) : sock_(ser), sr(writeBuffer_, 4096), os(sr),
		device(readBuffer_, 4096), is(device) //, oa(os)
	{
		Bind(std::string("IsInvAble"), IsInvAble);
		Bind(std::string("SVD"), SVD);
		Bind(std::string("Print"), Print);
	}
	template<typename F>
	void Bind(std::string fname, F f) {
		handlers_[fname] = std::bind(&CallProxy<F>, f, std::placeholders::_1,
			std::placeholders::_2);
	}
	void ReadComplete(const boost::system::error_code &ec, size_t bytes) {
		if (ec) Stop();
		if (!isStarted_) return;
		
		boost::archive::binary_iarchive ia(is);
		std::string funcname;
		ia >> funcname;
		int N; ia >> N;
		if (handlers_.find(funcname) == handlers_.end()) {
			XXX;//异常处理
			return;
		}
		boost::archive::binary_oarchive oa(os);
		handlers_[funcname](&oa, &ia);

		DoWrite();
	}

	void DoWrite() {
		sock_.async_write_some(boost::asio::buffer(writeBuffer_), std::bind(&Server::WriteComplete,
			shared_from_this(), std::placeholders::_1, std::placeholders::_2));
		DoRead();
	}
	boost::asio::ip::tcp::socket sock_;
	char readBuffer_[4096];
	char writeBuffer_[4096];

	boost::iostreams::basic_array_sink<char> sr;
	boost::iostreams::stream< boost::iostreams::basic_array_sink<char> > os;

	boost::iostreams::basic_array_source<char> device;
	boost::iostreams::stream<boost::iostreams::basic_array_source<char> > is;

	XXXXX//其他你需要的东西。
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章