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) $
e-mail



Hosted by uCoz