|
eao197 on the Web Сайт Евгения Охотникова |
[ Главная | Проекты | Описания | Об авторе | Лицензия ] |
C++ tricks: no_copy_paste
При реализации серверной части SMPP протокола возникла необходимость обработки трех вариаций команд BIND: BIND_TRANSMITTER, BIND_RECEIVER, BIND_TRANSCEIVER. Структуры каждой из команд совпадают, да и обработка команд делается аналогично. Только в процессе обработки нужно использовать разные классы, функции и строковые значения.
Сначала я сделал обработку команды BIND_TRANSMITTER без шаблонов, чтобы затем растиражировать получившийся код. Вот что получилось:
void
a_channel_t::on_bind_transmitter(
const smpp_pdu_1::pdu_t & what )
{
smpp_pdu_1::bind_transmitter_t parsed( what );
log_parsed_pdu( parsed );
smpp_pdu_1::pdu_t resp_pdu;
smpp_pdu_1::command_header_t::integer_t command_status =
smpp_pdu_1::command_status::rok;
std::string error_reason;
if( check_authentification( parsed.system_id(),
parsed.password(), parsed.system_type() ) )
{
// Роль TRANSMITTER-а должна быть разрешена и свободна.
smpp_roles_accessor_t smpp_roles( *m_smpp_roles );
role_status_t & role( smpp_roles.transmitter() );
if( role.is_enabled() )
if( role.is_free() )
{
// Клиент успешно подключился.
role.acquire( m_channel );
so_change_state( "st_bound_tx" );
smpp_pdu_1::bind_transmitter_resp_t ok_resp;
ok_resp.set_system_id(
m_authentification.m_resp_system_id );
ok_resp.write_to( resp_pdu, what.sequence_number() );
}
else
{
// Роль трансмиттера уже занята.
command_status = smpp_pdu_1::command_status::rbindfail;
error_reason = "TRANSMITTER уже подключен на другом канале";
}
else
{
// Для этого SMPP-входа трансмиттер не может использоваться.
command_status = smpp_pdu_1::command_status::rprohibited;
error_reason = "режим TRANSMITTER запрешен";
}
}
else
{
command_status = smpp_pdu_1::command_status::rbindfail;
error_reason = "не пройдена аутентификация";
}
if( smpp_pdu_1::command_status::rok != command_status )
{
so_log_1::err[ *this ]
[ so_log_1::n() && "TRANSMITTER-у отказано в подключении" ]
[ so_log_1::d() && error_reason ]();
// Отсылаем клиенту отрицательный ответ и не меняем
// своего состояния.
smpp_pdu_1::bind_transmitter_resp_t fail_resp;
fail_resp.write_to( resp_pdu, what.sequence_number() );
}
resp_pdu.set_command_status( command_status );
send_pdu( resp_pdu );
}
Нужно было написать еще два метода: on_bind_receiver, on_bind_transceiver. В которых bind_transmitter_t нужно было заменить на bind_receiver_t; bind_transmitter_resp_t на bind_receiver_resp_t; smpp_roles.transmitter() на smpp_roles.receiver(); "st_bound_tx" на "st_bound_rx"; "TRANSMITTER" на "RECEIVER" и т.д.
Понятно, что тиражирование кода с последующей обработкой напильником в данном случае дело гиблое. Особенно, если со временем придется что-то подкрутить. В результате применения шаблонов код нужных методов получился вот таким:
void
a_channel_t::on_bind_transmitter(
const smpp_pdu_1::pdu_t & what )
{
smpp_pdu_1::pdu_t resp_pdu(
impl::process_bind< smpp_pdu_1::bind_transmitter_t >(
what, *this, m_channel, m_authentification, *m_smpp_roles ) );
send_pdu( resp_pdu );
}
void
a_channel_t::on_bind_receiver(
const smpp_pdu_1::pdu_t & what )
{
smpp_pdu_1::pdu_t resp_pdu(
impl::process_bind< smpp_pdu_1::bind_receiver_t >(
what, *this, m_channel, m_authentification, *m_smpp_roles ) );
send_pdu( resp_pdu );
}
void
a_channel_t::on_bind_transceiver(
const smpp_pdu_1::pdu_t & what )
{
smpp_pdu_1::pdu_t resp_pdu(
impl::process_bind< smpp_pdu_1::bind_transceiver_t >(
what, *this, m_channel, m_authentification, *m_smpp_roles ) );
send_pdu( resp_pdu );
}
А применены были вот такие шаблоны:
namespace impl
{
//! Указатель на функцию доступа к статусу SMPP роли.
typedef role_status_t & (smpp_roles_accessor_t:: * role_accessor_t )();
//! Настройки на конкретные типы команд BIND.
template& class Request >
struct bind_traits_t
{};
//! Настройки на BIND_TRANSMITTER.
template<>
struct bind_traits_t< smpp_pdu_1::bind_transmitter_t >
{
//! Тип ответа.
typedef smpp_pdu_1::bind_transmitter_resp_tresponse_t;
//! Функция для доступа к состоянию SMPP-роли.
const role_accessor_trole_accessor;
//! Имя состояния, в которое нужно перейти в случае успеха.
const std::stringstate_for_success;
//! Имя роли для логирования.
const std::stringreadable_role_name;
bind_traits_t< smpp_pdu_1::bind_transmitter_t >()
: role_accessor( &smpp_roles_accessor_t::transmitter )
, state_for_success( "st_bound_tx" )
, readable_role_name( "TRANSMITTER" )
{}
};
//! Настройки на BIND_RECEIVER.
template<>
struct bind_traits_t< smpp_pdu_1::bind_receiver_t >
{
//! Тип ответа.
typedef smpp_pdu_1::bind_receiver_resp_tresponse_t;
//! Функция для доступа к состоянию SMPP-роли.
const role_accessor_trole_accessor;
//! Имя состояния, в которое нужно перейти в случае успеха.
const std::stringstate_for_success;
//! Имя роли для логирования.
const std::stringreadable_role_name;
bind_traits_t< smpp_pdu_1::bind_receiver_t >()
: role_accessor( &smpp_roles_accessor_t::receiver )
, state_for_success( "st_bound_rx" )
, readable_role_name( "RECEIVER" )
{}
};
//! Настройки на BIND_TRANSCEIVER.
template<>
struct bind_traits_t< smpp_pdu_1::bind_transceiver_t >
{
//! Тип ответа.
typedef smpp_pdu_1::bind_transceiver_resp_tresponse_t;
//! Функция для доступа к состоянию SMPP-роли.
const role_accessor_trole_accessor;
//! Имя состояния, в которое нужно перейти в случае успеха.
const std::stringstate_for_success;
//! Имя роли для логирования.
const std::stringreadable_role_name;
bind_traits_t< smpp_pdu_1::bind_transceiver_t >()
: role_accessor( &smpp_roles_accessor_t::transceiver )
, state_for_success( "st_bound_trx" )
, readable_role_name( "TRANSCEIVER" )
{}
};
//! Шаблон для облегчения обработки BIND_* для разных типов SMPP ролей.
/*!
\return PDU с ответом для клиента.
*/
template< class Request >
smpp_pdu_1::pdu_t
process_bind(
//! PDU с упакованным представлением BIND.
const smpp_pdu_1::pdu_t & what,
//! Агент, который обрабатывает BIND.
so_4::rt::agent_t & agent,
//! Канал, который обслуживат данный агент.
const so_4::rt::comm_channel_t & channel,
//! Параметры аутентификации.
const authentification_cfg_t & authentification,
//! Роли каналов SMPP.
smpp_roles_t & smpp_roles )
{
bind_traits_t< Request >traits;
Request parsed( what );
log_parsed_pdu( agent, parsed );
typename bind_traits_t< Request >::response_tresp;
smpp_pdu_1::command_header_t::integer_t command_status =
smpp_pdu_1::command_status::rok;
std::string error_reason;
if( check_authentification( agent, authentification,
parsed.system_id(), parsed.password(), parsed.system_type() ) )
{
// Роль должна быть разрешена и свободна.
smpp_roles_accessor_t smpp_roles( smpp_roles );
role_status_t & role( (smpp_roles.*(traits.role_accessor))() );
if( role.is_enabled() )
if( role.is_free() )
{
// Клиент успешно подключился.
role.acquire( channel );
agent.so_change_state( traits.state_for_success );
resp.set_system_id( authentification.m_resp_system_id );
}
else
{
// Роль уже занята.
command_status = smpp_pdu_1::command_status::rbindfail;
error_reason = traits.readable_role_name +
" уже подключен на другом канале";
}
else
{
// Для этого SMPP-входа роль не может использоваться.
command_status = smpp_pdu_1::command_status::rprohibited;
error_reason = "режим " + traits.readable_role_name +
" запрешен";
}
}
else
{
command_status = smpp_pdu_1::command_status::rbindfail;
error_reason = "не пройдена аутентификация";
}
if( smpp_pdu_1::command_status::rok != command_status )
{
so_log_1::err[ agent ]
[ so_log_1::n() << traits.readable_role_name
<< "-у отказано в подключении" ]
[ so_log_1::d() << error_reason ]();
}
smpp_pdu_1::pdu_t resp_pdu;
resp.write_to( resp_pdu, what.sequence_number() );
resp_pdu.set_command_status( command_status );
return resp_pdu;
}
} /* namespace impl */
В принципе, значения, которые определяются в специализации шаблона bind_traits_t можно было бы передавать в виде параметров в process_bind. Тогда бы методы on_bind_* имели бы приблизительно такой вид:
void
a_channel_t::on_bind_transmitter(
const smpp_pdu_1::pdu_t & what )
{
smpp_pdu_1::pdu_t resp_pdu(
impl::process_bind< smpp_pdu_1::bind_transmitter_t,
smpp_pdu_1::bind_transmitter_resp_t >(
what, *this, m_channel, m_authentification, *m_smpp_roles,
&smpp_roles_accessor_t::transmitter,
"st_bound_tx",
"TRANSMITTER" ) );
send_pdu( resp_pdu );
}
и количество template-классов бы сократилось. Но я подумал, что эти параметры являются деталями реализации process_bind. Поэтому их можно не выносить на уровень сигнатуры функции process_bind. Что позволит, например, изменить реализацию process_bind не меняя ее прототип.
© 2003-2004 Е.А. Охотников
$LastChangedDate: 2004-12-04 10:20:14 +0300 (Sat, 04 Dec 2004) $