PDA

Просмотр полной версии : Shadow RPC- легковесное RPC без использования CRT(c++)


sni4ok
29.08.2007, 21:48
Доброе время суток форумчане.
Сегодня я хочу поделиться с вами небольшой RPC библиотекой, которая поможет создавать
крохотные клиент-серверные приложения, не использующие хоть и мощную, но увы тяжеловатую crt языка c++
для примера, размер программы вида
int main(){malloc(0);}
получается порядка 30 киллобайт при статической линковки и полной оптимизации по размеру.
Полный же размер crt может достигать в программе нескольких сот киллобайт.
Разумеется для совсем небольших(в плане кода) программ это кажеться несколько накладно.
Более того, следующие конструкции языка также неявно подгружают crt:
исключения(увы и ах), полиморфизм, более того даже обычный operator new(nothrow) использует std::malloc, поэтому
new и delete перегружены в этой либе, но и это ещё не всё! даже при включении некоторых стандартных хедеров подгружается crt
например при инклуде <cassert>, размер exe'ика - релиза с оптимизацией на размер увеличивается с 1кб до 60.
При использовании некоторых алгоритмов- также используется crt, например при std::copy.

Данные наблюдения справедливы для MSVC8 и gcc(насчёт cassert'а для gcc не проверял), для других компиляторов не проверял,
но сомневаюсь что у них в этом плане сильно лучше.
Зато шаблоны и макросы доступны в полном обьёме:-)

Итак в типичном клиент-серверном взаимодействии(для простоты за сервер примем машину, на которой выполняются команды,
которые сервер получает от клиента, разумеется в больших системах часто эти понятия могут быть переплетены, но тут я это не рассматриваю)
есть прослойка отвечающая за отправку команд с клиента на сервер, и возращение результата работы выполнения этих команд назад.
За суть команды в этой библиотеки я принял некоторую свободную функцию, которая получает параметры(до 10 штук) по значению,
и возвращает некоторый результат(тоже по значению).
Так как использование исключений не допустимо в этой библиотеке(иначе это уже не rpc без crt), а каждая удалённая функция
может закончится провалом(например сервер повис, или просто провод обрубился) приходится вводить коды ошибок, любая rpc функция
регистрируемая в библиотеке должна принимать первым значением - ссылку на код возврата, не нулевое значение которого
означает, что выполнение функции завершилось неудачно, список ошибок можно посмотреть в enum'е shadow::Errors.
Регистрация функций происходит в файле shandow_instance.h и формат регистрации следующий:
enum FunctionType
{
//тут перечисляются некоторые идентификаторы функций, они используются исключительно внутри библиотеки и вы кроме
//этого файла их нигде не увидете
simple,
fff,
fcnt,

//последний элемент опять таки используется в либе для определения колличества регистрируемых функий.
last_function_id //this element must be in the end of enum
};

//дальше в этом же файле происходит регистрация самих функций, макросом SHADOW_FUNCTION
SHADOW_FUNCTION(function_name, function_id, function_ret, array_of_arguments)

где function_name -имя функции, которая будет вызываться удалённо(она должна быть в глобальном нэймспэйсе),
function_id -идентификатор который вы написали ранее в FunctionType(для каждой функции должен быть строго 1 уникальный идентификатор)
function_ret -тип возвращаемого значения функции
array_of_arguments -список аргументов типов функции, при этом самый первый, обязательный параметр функции(ShadowRet по ссылке)
в этот список не попадает, т.е запись
SHADOW_FUNCTION(qwerty, fcnt, shadow::stl::string, (2,(shadow::stl::string, long long)))
означает: регистрируем функцию с именем qwerty, идинтификатором fcnt, которая имеет прототип
shadow::stl::string qwerty(shadow::ShadowRet& ret, shadow::stl::string str, long long value)

для фундаментальных типов, а также для шаблонных классов shadow::stl::basic_string и shadow::stl::vector сериализацию прописывать не нужно,
для любых других пользовательских типов придётся прописывать функции
SerializeI и SerializeO, смысл их легко понять их исходников либы(файл shadow_rpc.hpp), но замечу что сериализация бинарная.

//Рабочий пример
файл shandow_instance.h:

#ifndef SHADOW_FUNCTION_TYPE
#define SHADOW_FUNCTION_TYPE

enum FunctionType
{
simple,
fff,
fcnt,

last_function_id //this element must be in the end of enum
};

#endif //SHADOW_FUNCTION_TYPE

//multiply times header including

SHADOW_FUNCTION(qwerty, fcnt, shadow::stl::string, (2,(shadow::stl::string, long long)))
SHADOW_FUNCTION(funciyo, simple, int, (1, (int)))
SHADOW_FUNCTION(ischo_funciya, fff, void, (0,()))


//файл simple_server.cpp

#include "stdafx.h"

#ifndef _DEBUG
#pragma comment(linker, "/SUBSYSTEM:WINDOWS")
#pragma comment(linker, "/ENTRY:main")
#pragma comment(linker, "/MERGE:.data=.text /MERGE:.rdata=.text")
#pragma comment(linker, "/SECTION:.text,EWR /STUB:../inc/dos_stub.dat")
#pragma optimize("gsy", on)
#endif

#define SHADOW_SERVER_HERE
//#define SHADOW_CLIENT_HERE
#include "shadow_rpc.hpp"
#include "shadow_misc.hpp"

int funciyo(shadow::ShadowRet& ret, int a){
return a;
}
shadow::stl::string qwerty(shadow::ShadowRet& ret, shadow::stl::string str, long long){
if(str == "preved krosavcheg")
return "preved medved";
else return "chto tebe?";
}
void ischo_funciya(shadow::ShadowRet& ret)
{
}

int main(int argc, char* argv[])
{
using namespace shadow;
for(;;){
TRY{
SyncServer ss(555);
CHECKO(ss);
stl::vector<char> buf1, buf2;
for(;;){
buf1.clear();
buf2.clear();
ss.Recv(buf1);
CHECKO(ss);
RunCmd(buf1, buf2);
ss.Send(buf2);
CHECKO(ss);
}
}
CATCH_BEGIN

CATCH_END
}

return 0;
}

//файл simple_client.cpp

#include "stdafx.h"

#ifndef _DEBUG
#pragma comment(linker, "/SUBSYSTEM:WINDOWS")
#pragma comment(linker, "/ENTRY:main")
#pragma comment(linker, "/MERGE:.data=.text /MERGE:.rdata=.text")
#pragma comment(linker, "/SECTION:.text,EWR /STUB:../inc/dos_stub.dat")
#pragma optimize("gsy", on)
#endif

//#define SHADOW_SERVER_HERE
#define SHADOW_CLIENT_HERE
#include "shadow_rpc.hpp"

int main(int argc, char* argv[])
{
using namespace shadow;
TRY{
SyncClient s("localhost", 555);
CHECKO(s);
RemoteCall<SyncClient> rc(s);
CHECKO(rc);
stl::string ret = rc.qwerty("preved krosavcheg",11);
CHECKO(rc);
_ASSERT(ret == "preved medved");
rc.ischo_funciya();
CHECKO(rc);
}
CATCH_BEGIN
_ASSERT(false);

CATCH_END
return 0;
}

на данный момент в библиотеке реализованы только синхронные tcp сокеты, но они сами по себе отношение к rpc не имеют,
но переделать на асинхронные в принципе не составит труда, благо исходники мне кажеться вполне понятны)))
что означают макросы типа TRY, CATCH_BEGIN, CHECKO и пр. - опять же таки лучше смотреть по исходникам,
но макросы вместо прямого кода в данном месте используются с потенциальной дальнейшей поддержкой исключений и трансляций их с сервера на клиент.
размер приведённого собранного сервера составляет 8, клиента 7.5кб, rpc'ёвый overhead на каждую новую функцию составляет порядка 300 байт как для сервера так и для клиента.
библиотека зависит по хедерам от буста (1.33 или выше), качать его можно отсюда http://boost.org/

исходники shadow_rpc и приведённого тут примера
http://snike.pochtamt.ru/flashcard/shadow_rpc.7z

з.ы библиотека распостраняется как есть, без каких либо гарантий и пр.

sni4ok
30.08.2007, 19:01
<removed>