【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//其他你需要的東西。
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章