сеть — преобразование NTOH в C ++ с диспетчером — очередь событий

Мы переписываем наш унаследованный код с C на C ++. В ядре нашей системы у нас есть TCP-клиент, который подключен к мастеру. Мастер будет непрерывно передавать сообщения. Каждое чтение сокета приведет, скажем, к N номеру сообщения в формате — {type, size, data[0]},

Теперь мы не копируем эти сообщения в отдельные буферы, а просто передаем указатель на начало сообщения, длину и shared_ptr в базовый буфер рабочим.

Устаревшая версия C была однопоточной и могла выполнять преобразование NTOH на месте, как показано ниже:

struct Message {
uint32_t something1;
uint16_t something2;
};

process (char *message)
Message *m = (message);
m->something1 = htonl(m->something1);
m->something2 = htons(m->something2);

А затем используйте сообщение.

Есть несколько проблем с регистрацией в новом коде.

  1. Поскольку мы отправляем сообщения разным работникам, каждый работник, выполняющий преобразование ntoh, вызовет проблемы с пропуском кеша, поскольку сообщения не выровнены по кешу, т. Е. Нет заполнения ч / б сообщений.

  2. Одно и то же сообщение может обрабатываться разными работниками — это тот случай, когда сообщение необходимо обработать локально, а также передать другому процессу. Здесь ретранслятору необходимо сообщение в исходном сетевом порядке, а локальная работа должна быть преобразована в порядок хостов. Очевидно, что сообщение не дублируется, и оба не могут быть удовлетворены.

Решения, которые приходят мне в голову:

  1. Дублируйте сообщение и отправьте одну копию для всех ретрансляторов, если таковые имеются. Выполните преобразование ntoh всех сообщений, принадлежащих одному и тому же буферу, в самом диспетчере перед отправкой — скажем, вызвав handler->ntoh(message); так что проблема с пропуском кеша решена.

  2. Отправьте каждому работнику оригинальную копию. Каждый работник скопирует сообщение в локальный буфер, а затем выполнит преобразование ntoh и использует его. Здесь каждый работник может использовать статический буфер для потока (thread_local) в качестве блокнота для копирования сообщения.

Теперь мой вопрос

  1. Вариант 1 способ сделать ntoh преобразование — C ++ sy? Я имею в виду, что требование выравнивания структуры будет отличаться от буфера символов. (у нас еще не было проблем с этим.). Использование схемы 2 должно быть хорошо в этом случае, так как рабочий буфер может иметь выравнивание max_align_t и, следовательно, должен быть применим по отношению к любой структуре. Но это влечет за собой копирование всего сообщения — которое может быть довольно большим (скажем, несколько К размер)

  2. Есть ли лучший способ справиться с ситуацией?

0

Решение

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

Мы можем обойти это несколькими способами, возможно, самым простым было бы ntoh основанный на однобайтовом указателе, который фактически всегда выровнен.

Мы можем скрыть неприятные детали за классами-обертками, которые будут брать указатель на начало сообщения и иметь средства доступа, которые будут ntoh соответствующее поле.

Как указано в комментариях, требуется, чтобы смещения определялись структурой C ++, поскольку именно так изначально создается сообщение, и оно может не упаковываться.

Во-первых, наш ntoh реализация, шаблонная, чтобы мы могли выбрать один по типу:

template <typename R>
struct ntoh_impl;

template <>
struct ntoh_impl<uint16_t>
{
static uint16_t ntoh(uint8_t const *d)
{
return (static_cast<uint16_t>(d[0]) << 8) |
d[1];
}
};

template <>
struct ntoh_impl<uint32_t>
{
static uint32_t ntoh(uint8_t const *d)
{
return (static_cast<uint32_t>(d[0]) << 24) |
(static_cast<uint32_t>(d[1]) << 16) |
(static_cast<uint32_t>(d[2]) <<  8) |
d[3];
}
};

template<>
struct ntoh_impl<uint64_t>
{
static uint64_t ntoh(uint8_t const *d)
{
return (static_cast<uint64_t>(d[0]) << 56) |
(static_cast<uint64_t>(d[1]) << 48) |
(static_cast<uint64_t>(d[2]) << 40) |
(static_cast<uint64_t>(d[3]) << 32) |
(static_cast<uint64_t>(d[4]) << 24) |
(static_cast<uint64_t>(d[5]) << 16) |
(static_cast<uint64_t>(d[6]) <<  8) |
d[7];
}
};

Теперь мы определим набор неприятных макросов, которые будут автоматически реализовывать средства доступа для заданного имени, ища члена с соответствующим именем в структуре proto (частная структура для каждого класса):

#define MEMBER_TYPE(MEMBER) typename std::decay<decltype(std::declval<proto>().MEMBER)>::type

#define IMPL_GETTER(MEMBER) MEMBER_TYPE(MEMBER) MEMBER() const { return ntoh_impl<MEMBER_TYPE(MEMBER)>::ntoh(data + offsetof(proto, MEMBER)); }

Наконец, у нас есть пример реализации структуры сообщения, которую вы дали:

class Message
{
private:
struct proto
{
uint32_t something1;
uint16_t something2;
};

public:
explicit Message(uint8_t const *p) : data(p) {}
explicit Message(char const *p) : data(reinterpret_cast<uint8_t const *>(p)) {}

IMPL_GETTER(something1)
IMPL_GETTER(something2)

private:
uint8_t const *data;
};

Сейчас Message::something1() а также Message::something2() реализованы и будут читать из data указатель на те же смещения они оказываются в Message::proto,

Предоставление реализации в заголовке (эффективно встроенной) может встраивать весь ntoh последовательность на сайте вызова каждого доступа!

Этот класс не владеет распределением данных, из которого он построен. Предположительно, вы могли бы написать базовый класс, если здесь есть детали для поддержания прав собственности.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]