Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

RMI средствами С и boost.preprocessor

Anna | 24.06.2014 | нет комментариев
Это моя первая публикация на сем источнике, посему, умоляю отнестись с пониманием к допущенным мной ошибкам.

RMI — крайне тривиальная задача для ЯП, поддерживающих интроспекцию. Но, С , к сожалению, к ним не относится.В данной публикации я хочу продемонстрировать вероятность реализации крайне юзабильной RMI средствами С препроцессора.

Постановка задачи

1. Предоставить максимально примитивный синтаксис, чтоб немыслимо было допустить ошибку.
2. Распознавание(связывание) процедур должна быть спрятана от пользователя для того, чтоб немыслимо было допустить ошибку.
3. Синтаксис не должен накладывать ограничения на используемые С типы.
4. Должна присутствовать вероятность версионности процедур, но, так, чтоб не ломалась совместимость с теснее работающими заказчиками.

Касательно четвертого пункта:
К примеру, у нас теснее есть две процедуры add/div по одной версии всякой. Мы хотим для add добавить новую версию. Если легко добавить еще одну версию — у нас поплывут ID`ы процедур, о которых знают клиентские программы, собранные до внесения этого метаморфозы.

Выбор инструмента

Т.к. финальный итог предполагается применять коллективно с С кодом, вариантов я вижу три:

  • Изобретаем синтаксис и пишем свой кодогенератор.
  • Используем С препроцессор.
  • Ищем что-то готовое и допиливаем под себя(если необходимо).

Выскажусь о всяком из вариантов соответственно:

  • Для чего добавочная стадия кодогенерации?
  • Препроцессор я люблю, и Зачастую его использую.
  • Трата времени и сил. И, не ясно, будет ли в этом толк.

Касательно первого, второго и третьего пунктов требований — препроцессорный вариант подходит.

Выходит, выбор сделан — используем препроцессор. И да, разумеется boost.preprocessor.

Немножко о препроцессоре

Типы данных С препроцессора:

Типов, как видно, больше чем довольно.
Немножко подумав, почитав про вероятности и ограничения всякого их них, а также учтя желаемую простоту синтаксиса и неосуществимость допустить ошибку — выбор был сделан в пользу sequences и tuples.

Несколько объясняющих примеров.
(a)(b)(c) — sequence. Здесь, мы описали sequence, состоящий из 3 элементов.
(a) — также sequence, но состоящий из одного элемента. (внимание!)
(a)(b, c)(d, e, f) — вновь sequence, но состоящий из 3 tuples. (обратите внимание на 1-й элемент — уловочка, впрочем, но это и правда tuple)
(a)(b, c)(d, (e, f)) — вновь же sequence, и так же состоящий из 3 tuples. Но! Конечный tuple состоит их 2-х элементов: 1) всякого элемента, 2) tuple.
И, напоследок, такой пример: (a)(b, c)(d, (e, (f)(g))) — здесь уж разберитесь сами ;)
Как видно, все и воистину невозможно легко.

Прототипируем синтаксис

Позже недолгого раздумья, вырисовывается такой синтаксис:

(proc_name0, // имя процедуры
   (signature_arg0, signature_arg1, signature_argN) // первая версия процедуры
   (signature_arg0) // вторая версия процедуры
)
(proc_name1, // имя процедуры
   (signature_arg0, signature_arg1) // исключительная версия этой процедуры
)
(proc_name2, // имя процедуры
   () // исключительная версия этой процедуры (без доводов)
)

Ну… крайне употрибительно, впрочем.

Некоторые детали реализации

Т.к. одно из требований — версионность процедур, да еще и такая, чтоб не ломалась совместимость с теснее существующими заказчиками — нам, для идентификации процедур, потребуются два ID`а. 1-й — ID процедуры, 2-й — ID версии.

Поясню на примере.
Возможен, это изложение API нашего обслуживания. Возможен, у нас теснее есть клиентские программы, использующие данный API.

(proc_name0, // procID=0
   (signature_arg0, signature_arg1) // sigID=0
)
(proc_name1, // procID=1
   (signature_arg0, signature_arg1) // sigID=0
)
(proc_name2, // procID=2
   () // sigID=0
)

Сейчас, для proc_name0() нам необходимо добавить еще одну версию с иной сигнатурой.

(proc_name0, // procID=0
   (signature_arg0, signature_arg1) // sigID=0
   (signature_arg0, signature_arg1, signature_arg2) // sigID=1
)
(proc_name1, // procID=1
   (signature_arg0, signature_arg1) // sigID=0
)
(proc_name2, // procID=2
   () // sigID=0
)

Таким образом, у нас возник новейший ID версии процедуры, в то время как бывший остался без изменений.
Было: (0:0), стало: (0:0)(0:1)
Т.е. именно этого мы и пытались добиться. Бывшие заказчики как применяли (0:0), так и дальше будут применять эти идентификаторы, не переживая о том, что возникли новые версии этих процедур.
Также условимся в том, что все новые процедуры необходимо добавлять в конец.

Дальше, нам необходимо позаботиться о том, чтоб ID`ы механически проставлялись на обоих сторонах обслуживания. Запросто! — легко используем одну и ту же описанную последовательность двукратно, для генерации клиентской и серверной сторон!

Самое время представить, как мы хотим видеть все это в финальном счете:

MACRO(
	client_invoker, // name of the client invoker implementation class
	((registration, // procedure name
		((std::string, std::string)) // message : registration key
	))
	((activation,
		((std::string)) // message
	))
	((login,
		((std::string)) // message
	))
	((logout,
		((std::string)) // message
	))
	((users_online,
		((std::vector<std::string>)) // without args
	))
	,
	server_invoker, // name of the server invoker implementation class
	((registration,
		((std::string)) // username
	))
	((activation,
		((std::string, std::string, std::string)) // registration key : username : password
	))
	((login,
		((std::string, std::string)) // username : password
	))
	((logout,
		(()) // without args
	))
	((users_online,
		(()) // without args
	))
)

Чтоб не было путаницы в том, кто ведущий, а кто ведомый — условимся так, что процедуры, описываемые на одной из сторон, являются реализациями, находящимися на противоположной стороне. Т.е., к примеру,client_invoker::registration(std::string, std::string) говорит нам о том, что реализация этой процедуры будет находиться на стороне сервера, в то время как интерфейс к этой процедуре будет находиться на стороне заказчика, и напротив.
(двойные круглые скобки мы используем потому, что препроцессор при образовании довода для нашего MACRO(), развернет нами описанное API. это дозволено побороть, но не знаю, необходимо ли?..)

Вывод

Из приведенного выше макровызова, будет сгенерирован код, находящийся под спойлером.

Код

namespace yarmi {

template<typename Impl, typename IO = Impl>
struct client_invoker {
	client_invoker(Impl &impl, IO &io)
		:impl(impl)
		,io(io)
	{}

	void registration(const std::string &arg0) {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(0)
			& static_cast<std::uint8_t>(0)
			& arg0;
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}
	void activation(const std::string &arg0, const std::string &arg1, const std::string &arg2) {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(1)
			& static_cast<std::uint8_t>(0)
			& arg0
			& arg1
			& arg2;
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}
	void login(const std::string &arg0, const std::string &arg1) {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(2)
			& static_cast<std::uint8_t>(0)
			& arg0
			& arg1;
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}
	void logout() {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(3)
			& static_cast<std::uint8_t>(0);
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}
	void users_online() {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(4)
			& static_cast<std::uint8_t>(0);
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}
	void yarmi_error(const std::uint8_t &arg0, const std::uint8_t &arg1, const std::string &arg2) {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(5)
			& static_cast<std::uint8_t>(0)
			& arg0
			& arg1
			& arg2;
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}
	void invoke(const char *ptr, std::size_t size) {
		std::uint8_t call_id, call_version;
		static const char* names[] = {
			 "registration"
			,"activation"
			,"login"
			,"logout"
			,"users_online"
			,"yarmi_error"
		};
		static const std::uint8_t versions[] = { 0, 0, 0, 0, 0, 0 };
		try {
			yas::binary_mem_iarchive ia(ptr, size, yas::no_header);
			ia & call_id
				& call_version;

			if ( call_id < 0 || call_id > 5 ) {
				char errstr[1024] = {0};
				std::snprintf(
					 errstr
					,sizeof(errstr)
					,"%s::%s(): bad call_id %d"
					,"client_invoker"
					,__FUNCTION__
					,static_cast<int>(call_id)
				);
				throw std::runtime_error(errstr);
			}
			if ( call_version > versions[call_id] ) {
				char errstr[1024] = {0};
				std::snprintf(
					 errstr
					,sizeof(errstr)
					,"%s::%s(): bad call_version %d for call_id %d(%s::%s())"
					,"client_invoker"
					,__FUNCTION__
					,static_cast<int>(call_version)
					,static_cast<int>(call_id)
					,"client_invoker"
					,names[call_id]
				);
				throw std::runtime_error(errstr);
			}

			switch ( call_id ) {
				case 0: {
					switch ( call_version ) {
						case 0: {
							std::string arg0;
							std::string arg1;
							ia & arg0
								& arg1;

							impl.on_registration(arg0, arg1);
						};
						break;
					}
				};
				break;
				case 1: {
					switch ( call_version ) {
						case 0: {
							std::string arg0;
							ia & arg0;

							impl.on_activation(arg0);
						};
						break;
					}
				};
				break;
				case 2: {
					switch ( call_version ) {
						case 0: {
							std::string arg0;
							ia & arg0;

							impl.on_login(arg0);
						};
						break;
					}
				};
				break;
				case 3: {
					switch ( call_version ) {
						case 0: {
							std::string arg0;
							ia & arg0;

							impl.on_logout(arg0);
						};
						break;
					}
				};
				break;
				case 4: {
					switch ( call_version ) {
						case 0: {
							std::vector<std::string> arg0;
							ia & arg0;

							impl.on_users_online(arg0);
						};
						break;
					}
				};
				break;
				case 5: {
					switch ( call_version ) {
						case 0: {
							std::uint8_t arg0;
							std::uint8_t arg1;
							std::string arg2;
							ia & arg0
								& arg1
								& arg2;

							impl.on_yarmi_error(arg0, arg1, arg2);
						};
						break;
					}
				};
				break;
			}
		} catch (const std::exception &ex) {
			char errstr[1024] = {0};
			std::snprintf(
				 errstr
				,sizeof(errstr)
				,"std::exception is thrown when %s::%s() is called: '%s'"
				,"client_invoker"
				,names[call_id]
				,ex.what()
			);
			yarmi_error(call_id, call_version, errstr);
		} catch (...) {
			char errstr[1024] = {0};
			std::snprintf(
				 errstr
				,sizeof(errstr)
				,"unknown exception is thrown when %s::%s() is called"
				,"client_invoker"
				,names[call_id]
			);
			yarmi_error(call_id, call_version, errstr);
		}
	}
private:
	Impl &impl;
	IO &io;
}; // struct client_invoker

template<typename Impl, typename IO = Impl>
struct server_invoker {
	server_invoker(Impl &impl, IO &io)
		:impl(impl)
		,io(io)
	{}

	void registration(const std::string &arg0, const std::string &arg1) {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(0)
			& static_cast<std::uint8_t>(0)
			& arg0
			& arg1;
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}
	void activation(const std::string &arg0) {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(1)
			& static_cast<std::uint8_t>(0)
			& arg0;
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}
	void login(const std::string &arg0) {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(2)
			& static_cast<std::uint8_t>(0)
			& arg0;
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}
	void logout(const std::string &arg0) {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(3)
			& static_cast<std::uint8_t>(0)
			& arg0;
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}
	void users_online(const std::vector<std::string> &arg0) {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(4)
			& static_cast<std::uint8_t>(0)
			& arg0;
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}
	void yarmi_error(const std::uint8_t &arg0, const std::uint8_t &arg1, const std::string &arg2) {
		yas::binary_mem_oarchive oa(yas::no_header);
		oa & static_cast<std::uint8_t>(5)
			& static_cast<std::uint8_t>(0)
			& arg0
			& arg1
			& arg2;
		yas::binary_mem_oarchive pa;
		pa & oa.get_intrusive_buffer();
		io.send(pa.get_shared_buffer());
	}

	void invoke(const char *ptr, std::size_t size) {
		std::uint8_t call_id, call_version;
		static const char* names[] = {
			 "registration"
			,"activation"
			,"login"
			,"logout"
			,"users_online"
			,"yarmi_error"
		};
		static const std::uint8_t versions[] = { 0, 0, 0, 0, 0, 0 };

		try {
			yas::binary_mem_iarchive ia(ptr, size, yas::no_header);
			ia & call_id
				& call_version;

			if ( call_id < 0 || call_id > 5 ) {
				char errstr[1024] = {0};
				std::snprintf(
					 errstr
					,sizeof(errstr)
					,"%s::%s(): bad call_id %d"
					,"server_invoker"
					,__FUNCTION__
					,static_cast<int>(call_id)
				);
				throw std::runtime_error(errstr);
			}
			if ( call_version > versions[call_id] ) {
				char errstr[1024] = {0};
				std::snprintf(
					 errstr
					,sizeof(errstr)
					,"%s::%s(): bad call_version %d for call_id %d(%s::%s())"
					,"server_invoker"
					,__FUNCTION__
					,static_cast<int>(call_version)
					,static_cast<int>(call_id)
					,"server_invoker"
					,names[call_id]
				);
				throw std::runtime_error(errstr);
			}

			switch ( call_id ) {
				case 0: {
					switch ( call_version ) {
						case 0: {
							std::string arg0;
							ia & arg0;

							impl.on_registration(arg0);
						};
						break;
					}
				};
				break;
				case 1: {
					switch ( call_version ) {
						case 0: {
							std::string arg0;
							std::string arg1;
							std::string arg2;
							ia & arg0
								& arg1
								& arg2;

							impl.on_activation(arg0, arg1, arg2);
						};
						break;
					}
				};
				break;
				case 2: {
					switch ( call_version ) {
						case 0: {
							std::string arg0;
							std::string arg1;
							ia & arg0
								& arg1;

							impl.on_login(arg0, arg1);
						};
						break;
					}
				};
				break;
				case 3: {
					switch ( call_version ) {
						case 0: {
							impl.on_logout();
						};
						break;
					}
				};
				break;
				case 4: {
					switch ( call_version ) {
						case 0: {
							impl.on_users_online();
						};
						break;
					}
				};
				break;
				case 5: {
					switch ( call_version ) {
						case 0: {
							std::uint8_t arg0;
							std::uint8_t arg1;
							std::string arg2;
							ia & arg0
								& arg1
								& arg2;

							impl.on_yarmi_error(arg0, arg1, arg2);
						};
						break;
					}
				};
				break;
			}
		} catch (const std::exception &ex) {
			char errstr[1024] = {0};
			std::snprintf(
				 errstr
				,sizeof(errstr)
				,"std::exception is thrown when %s::%s() is called: '%s'"
				,"server_invoker"
				,names[call_id]
				,ex.what()
			);
			yarmi_error(call_id, call_version, errstr);
		} catch (...) {
			char errstr[1024] = {0};
			std::snprintf(
				 errstr
				,sizeof(errstr)
				,"unknown exception is thrown when %s::%s() is called"
				,"server_invoker"
				,names[call_id]
			);
			yarmi_error(call_id, call_version, errstr);
		}
	}
private:
	Impl &impl;
	IO &io;
}; // struct server_invoker
} // ns yarmi

(в качестве сериализации применяется иной мой план — YAS)

Как бонус, была добавлена системная процедура yarmi_error() — применяется для сообщения противоположной стороне о том, что при попытке произвести вызов случилась оплошность. Посмотрите наблюдательно, в client_invoker::invoke(), десериализация и вызов обернуты в try{}catch() блок, а вcatch() блоках производится вызов yarmi_error(). Таким образом, если при десериализации либо вызове процедуры возникнет исключение — оно будет удачно перехвачено catch() блоком, и информация об исключении будет отправлена вызывающей стороне. То же самое будет протекать и в противоположном направлении. Т.е. если сервер вызвал у заказчика процедуру, в ходе вызова которой появилось исключение — заказчик отправит серверу информацию об ошибке, также добавочно известив ID и версию вызова, в котором появилось исключение. Но, применять yarmi_error() вы можете и сами, ничто этого не mk!

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB