【C++】Boost.Asio+Serialization 建立RPC demo(客戶端)

RPC服務(Remote Procedure Call),可以從客戶端要求服務端進行相關業務,從服務端獲取相應的答案,調用方法類似普通的函數調用。RPC在分佈式系統中的系統環境建設和應用程序設計中有着廣泛的應用,比如:分佈式操作系統的進程間通訊;構造分佈式計算的軟件環境;遠程數據庫服務等。
這裏嘗試用Boost.Asio和Serialization搭建一個RPC demo,編譯平臺是VS 2017,需要用到C++11及以上標準。Asio負責開發異步服務端和客戶端,Serialization用於傳輸函數和參數的序列化/反序列化。本文先介紹RPC 客戶端的相關內容,客戶端使用簡單的同步模式開發。具體參見github:

統一調用函數聲明

RPC客戶端的核心在於建立一個統一的RPC調用函數形式,用以靈活調用函數。所有RPC方法通過call函數調用,具體函數調用通過函數名字符串來指代,後續跟隨具體的參數。聲明如下:

template<typename T, typename... Params>
Respond<T> Call(std::string funcName, Params... ps)

這是一個帶有可變模板的函數,用以輸入不同長度、不同類別的參數。返回值爲Respond,便以統一返回類型。可調用函數的返回類型可能爲void,我用int來替換,需要使用type_traits方法:

template<typename T>
struct type_transfer { typedef T type; };
template<>
struct type_transfer<void> { typedef int type; };

template<typename T>
struct Respond {
	Respond() { errorCode_ = 0; }
	int errorCode_;
	typename type_transfer<T>::type val_;
};
Call函數實現
template<typename T, typename... Params>
Respond<T> Call(std::string funcName, Params... ps) {
	memset(writeBuffer_, 0, 4096);//oa所用的緩衝
	memset(readBuffer_, 0, 4096);//ia所用的緩衝
	os.close();//重初始化
	os.open(sr);
	boost::archive::binary_oarchive oa(os);
	oa << funcName;
	//parameter number
	int N = int(sizeof...(Params));//參數個數
	oa << N;

	std::tuple<typename std::decay<Params>::type...> args = std::make_tuple(ps...);//將所有參數存入tuple
	PackageParams(oa, args);//對所有參數進行序列化

	sock_.write_some(boost::asio::buffer(writeBuffer_),ec_);//同步傳輸
	is.close();//重初始化
	is.open(device);
	sock_.read_some(boost::asio::buffer(readBuffer_), ec_);//同步讀入
	Respond<T> res;
	boost::archive::binary_iarchive ia(is);
	ia >> res;//反序列化
	return res;
}

需要關注的方法有:

  1. template<typename… Params> sizeof…(Params),C++11標準新用法,可以獲取參數個數,但是返回的數值不能直接當作模板。…符號在前面
  2. std::tuple<typename std::decay::type…>,將所有參數放入std::tuple,std::decay用於去掉const,&,&&模板符號。…符號在type後面。(這位置有點麻煩)
    此外,PackageParams是一個自定義函數用於展開參數並一一處理,下面來重點介紹。
展開參數列表並序列化

PackageParams函數定義如下

template<typename... Args>
void PackageParams(boost::archive::binary_oarchive &oa, const std::tuple<Args...>& t)
{
	PackageParamsImpl(oa, t, std::index_sequence_for<Args...>{});
}

typename… Args表示所有參數的模板。std::index_sequence_for<Args…>{}根據模板數量N,建立一個0~N的模板序列,注意這裏的數字序列可以當作模板!
實際的操作函數爲:

template<typename Tuple, std::size_t... Is>
void PackageParamsImpl(boost::archive::binary_oarchive &oa, const Tuple& t, std::index_sequence<Is...>)
{
	std::initializer_list<int>{ ((oa << std::get<Is>(t)), 0)... };
}

std::index_sequence_for<Args…>對應這個函數是std::size_t… Is,Is是模板數字序列,注意是模板。td::index_sequence_for生成的對象是std::index_sequence<Is…>,參考type_traits方法,這個對象沒有實質內容,僅僅是把Is…模板數字序列引進來。
比較讓人驚訝的是std::initializer_list的用法。這裏直接展開了參數列表,使得oa對每個參數都進行了一次序列化操作。這個用法我在網上搜索,相關的內容很少。std::initializer_list{ ((oa << std::get(t)), 0)… };最後返回的是全爲0值的列表,oa << std::get(t)返回的是一個binary_oarchive引用,然而這裏僅僅保留0。如果用((oa << std::get(t)))或者(0, (oa << std::get(t)))都會報錯。此外如果不寫 < int >,也會報錯。

調用方法
auto client = Client::New(service);
client->Start(ep);
auto res2 = client->Call<SVDData>(std::string("SVD"), mat2);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章