Re[10]: Protoctor - communication protocol constructor
От: wander  
Дата: 27.01.14 17:07
Оценка: 12 (1) +1
Здравствуйте, niXman, Вы писали:


X>доку читал, но ничего подобного не обнаружил.

Немудрено. Это там запрятано специально, так как не позиционируется как "официальный" способ. Тем не менее, это не хак, все в рамках стандартного API. Лично мне в нашем проекте это сильно помогло связать две разные (с разными форматами сообщений) подсистемы в один информационный слой.

struct type {
   int v;
   std::string s;
   std::pair<std::string, std::map<int, std::string>> m;
};

proto
Хочу отметить, что в протобуфере нет понятий "map", поэтому map сериализуем как repeated field.

код
Выглядит страшновато, но это только на первый взгляд. Я намеренно не писал никаких абстракций сверх этого, потому что: 1) мне банально лень 2) это загромоздит пример.
На деле же, пишутся довольно прозрачные обертки для основных сущностей и код превращается в нечто вроде этого:
ProtoMessage mes;

mes["first"]  = 1;
mes["second"] = "test";

вывод
Re[11]: Protoctor - communication protocol constructor
От: niXman Ниоткуда https://github.com/niXman
Дата: 06.02.14 11:03
Оценка:
Здравствуйте, wander, Вы писали:

выглядит жутко %)

скажи, а в доке это где-то описано?
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[12]: Protoctor - communication protocol constructor
От: wander  
Дата: 06.02.14 19:42
Оценка: 12 (1) +1
Здравствуйте, niXman:

X>выглядит жутко %)

С обертками намного приятнее. Но надо учитывать, что это довольно низкоуровневый функционал. Обычно он так и выглядит

X>скажи, а в доке это где-то описано?

Ну я же это не сам придумал . Где-то есть, правда найти сходу не получилось, но точно есть. Пример, который я дал, компилируемый. Можешь запустить, посмотреть — он прекрасно работает.
Re[12]: Protoctor - communication protocol constructor
От: wander  
Дата: 08.02.14 00:00
Оценка:
Здравствуйте, niXman.

Я тут обнаружил, что запостил в исходном сообщении ссылку два раза на одно и тоже. Хотя задумывалось не так: Вот прото-файл, который потерялся:

message MapEntry
{
required int32 key = 1;
required string value = 2;
}

message PairEntry
{
required string first = 1;
repeated MapEntry second = 2;
}

message Type
{
required int32 v = 1;
required string s = 2;
required PairEntry m = 3;
}

Re: Protoctor - communication protocol constructor
От: Lazin Россия http://evgeny-lazin.blogspot.com
Дата: 21.04.14 10:02
Оценка:
Здравствуйте, niXman, Вы писали:

X>Хотелось бы получить пожелания/идеи/рекомендации/фидбэк.


1. Можно сделать что-то вроде IDL, который не будет требовать отдельного шага кодогенерации в С++, но позволит написать генераторы кода для других языков. Выносим описания в отдельный файл с расширением .yarmi и говорим что это такой IDL, в с++ коде подрубаем этот файл обычным include-ом и обрабатываем препроцессором. Ну и нужно ограничить и спрятать использование типов С++ в IDL. В общем, вместо вот этого — https://github.com/niXman/yarmi/blob/master/examples/chat/protocol.hpp будет что-то такое:

файл protocol.hpp:


#include <yarmi/yarmi.hpp>

#include "protocol.yarmi"

#endif // _yarmi__chat__protocol_hpp



файл protocol.yarmi

YARMI_CONSTRUCT(
    (yarmi),        // client invoker namespace
    client_invoker, // name of the client invoker
    (registration , on_registration, (string)                           ) // username
    (activation   , on_activation  , (string, string, string)           ) // registration key : username : password
    (login        , on_login       , (string, string)                   ) // username : password
    (logout       , on_logout      , ()                                 ) // without args
    (users_online , on_users_online, ()                                 ) // without args
    (users_online , on_users_online, (string)                           ) // substring of username
    ,
    (yarmi),        // server invoker namespace
    server_invoker, // name of the server invoker
    (registration , on_registration, (string, std::string)              ) // message : registration key
    (activation   , on_activation  , (string)                           ) // message
    (login        , on_login       , (string)                           ) // message
    (logout       , on_logout      , (string)                           ) // message
    (users_online , on_users_online, (array<string>)                    ) // vector of usernames
);


2. Написать нормальный серверный код. Во первых, не нужно читать заголовок и тело сообщения отдельными вызовами, нужно читать сколько пришло а затем анализировать — можно ли прочитать сообщение или сообщение еще не полностью получено. Во вторых — не нужно выполнять пользовательский код внутри io_service-а. У тебя там сейчас все синхронно работает, несмотря на использование асинхронного api. Пока работает пользовательский код, можно начинать читать из сокета следующее сообщение, не дожидаясь окончания обработки. У тебя же чтение сокета и обработка данных, полученных из сокета выполняются последовательно, даже несмотря на то, что у тебя протокол симплексный.
Re[2]: Protoctor - communication protocol constructor
От: niXman Ниоткуда https://github.com/niXman
Дата: 14.05.14 01:26
Оценка:
Здравствуйте, Lazin, Вы писали:

давно не заходил сюда, ибо обычно тут мертво

L>1. Можно сделать что-то вроде IDL, который не будет требовать отдельного шага кодогенерации в С++, но позволит написать генераторы кода для других языков. Выносим описания в отдельный файл с расширением .yarmi и говорим что это такой IDL, в с++ коде подрубаем этот файл обычным include-ом и обрабатываем препроцессором.

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

L>Ну и нужно ограничить и спрятать использование типов С++ в IDL.

а какие обычно используются типы, кроме вообще стандартных(целых,строк,массивов) ?

L>2. Написать нормальный серверный код. Во первых, не нужно читать заголовок и тело сообщения отдельными вызовами, нужно читать сколько пришло а затем анализировать — можно ли прочитать сообщение или сообщение еще не полностью получено.

все равно нужно знать где заголовок сообщения, для того чтоб определить размер тела сообщения, для того чтоб определить когда можно производить инвокинг. или что?

L>Во вторых — не нужно выполнять пользовательский код внутри io_service-а. У тебя там сейчас все синхронно работает, несмотря на использование асинхронного api. Пока работает пользовательский код, можно начинать читать из сокета следующее сообщение, не дожидаясь окончания обработки.

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

по поводу "не нужно выполнять пользовательский код внутри io_service-а".
что, завести отдельный поток?
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[3]: Protoctor - communication protocol constructor
От: Lazin Россия http://evgeny-lazin.blogspot.com
Дата: 14.05.14 11:37
Оценка:
Здравствуйте, niXman, Вы писали:

L>>1. Можно сделать что-то вроде IDL, который не будет требовать отдельного шага кодогенерации в С++, но позволит написать генераторы кода для других языков. Выносим описания в отдельный файл с расширением .yarmi и говорим что это такой IDL, в с++ коде подрубаем этот файл обычным include-ом и обрабатываем препроцессором.

X>да, уже начал это делать, но несколько иначе, а именно — ввел новый синтаксис.
X>хотя, признаюсь, — твое предложение более логично
Ну вообще, я тут подумал — наверное это не очень хорошо, так как тебе ведь не только стандартные типы нужно сериализовать, но и типы пользователя. Например protobuf и thrift позволяют описывать сложные структуры данных, в том числе вложенные и для каждого языка генерируется своя релизация. С этим подходом пользователю придется самостоятельно описывать структуры для других языков. Наверное все же лучше внешний idl.

L>>Ну и нужно ограничить и спрятать использование типов С++ в IDL.

X>а какие обычно используются типы, кроме вообще стандартных(целых,строк,массивов) ?
Посмотри доку по thrift. Там есть набор стандартных типов, int-ы, float-ы и тд. Есть массивы и map-ы, помимо этого — можно определять пользовательские структуры и enum-ы.

L>>2. Написать нормальный серверный код. Во первых, не нужно читать заголовок и тело сообщения отдельными вызовами, нужно читать сколько пришло а затем анализировать — можно ли прочитать сообщение или сообщение еще не полностью получено.

X>все равно нужно знать где заголовок сообщения, для того чтоб определить размер тела сообщения, для того чтоб определить когда можно производить инвокинг. или что?
Берешь длинный буфер и вызываешь async_read(buffer, buffer_len, ...) в буффер приходит какое-то количество данных — меньше или равно buffer_len, далее, ты ставишь его в очередь, берешь новый буффер и делаешь с ним то же самое. Параллельный поток или пулл потоков вычерпывает буферы из очереди и парсит, парсер должен быть написан с расчетом на то, что в одном буфере может прийти множество сообщений, при чем не целое их количество. Это довольно тривиально, ты берешь первый буфер, он гарантированно начинается с заголовка сообщения (если его размер больше размера этого заголовка), потом смотришь, если длина сообщения меньше чем количество данных, записаных в буффер — то значит сообщение целиком там — десериализуешь его и переходишь к следующему, если сообщение не полностью прочитано (размер сообщения > размер оставшихся в буфере данных), берешь из очереди следующий буфер и тд. Так ты будешь экономить системные вызовы, так как один recv сможет прочитать сразу несколько сообщений.

L>>Во вторых — не нужно выполнять пользовательский код внутри io_service-а. У тебя там сейчас все синхронно работает, несмотря на использование асинхронного api. Пока работает пользовательский код, можно начинать читать из сокета следующее сообщение, не дожидаясь окончания обработки.

X>допустим, по прочтению сообщения, я снова запускаю чтение следующего сообщения, и выполняю инвок уже прочитанного. допустим, что следующее сообщение я прочитал бестрее чем выполнился пользовательский код, и в этом случаем мне все равно нужно ждать возврата из пользовательского кода. или поясни.
X>по поводу "не нужно выполнять пользовательский код внутри io_service-а".
X>что, завести отдельный поток?
Как правило, занимать пулл потоков, обслуживающий ввод-вывод, вычислениями — плохо для производительности. Поэтому вычисления выносят в отедльный поток или отдельный пулл потоков в котором происходит парсинг полученных данных и обработка результатов. Желательно использовать ограниченную очередь, чтобы в том случае, когда обработка у тебя медленнее чем чтение, не сжиралась вся память. Если представить, что с твоим сервером работает один клиент, то все равботает на стороне сервера примерно так: чтение заголовка сообщения — чтение тела сообщения — обработка. Это по сути синхронная работа, несмотря на то, что используется асинхронный апи. Напрочь игнорируется тот факт, что пока ты обрабатываешь сообщение номер N, ты можешь читать сообщение N+1(+2, 3,...). Т.е. ты пишешь сервер без read ahead, хотя read ahead у тебя возможен, так как RPC функции у тебя не могут возвращать результаты, т.е. поток сообщений односторонний.
Re[4]: Protoctor - communication protocol constructor
От: niXman Ниоткуда https://github.com/niXman
Дата: 14.05.14 13:35
Оценка:
Здравствуйте, Lazin, Вы писали:

L>Ну вообще, я тут подумал — наверное это не очень хорошо, так как тебе ведь не только стандартные типы нужно сериализовать, но и типы пользователя. Например protobuf и thrift позволяют описывать сложные структуры данных, в том числе вложенные и для каждого языка генерируется своя релизация. С этим подходом пользователю придется самостоятельно описывать структуры для других языков. Наверное все же лучше внешний idl.

позволить описывать пользовательские данные я могу и средствами препроцессора, используя префикс для каждого элемента описания.
например так:
YARMI_CONSTRUCT(
    (yarmi),        // client invoker namespace
    client_invoker, // name of the client invoker
    (proc(registration , on_registration, (string)                           )) // username
    (proc(activation   , on_activation  , (string, string, string)           )) // registration key : username : password
    (proc(login        , on_login       , (string, string)                   )) // username : password
    (proc(logout       , on_logout      , ()                                 )) // without args
    (proc(users_online , on_users_online, ()                                 )) // without args
    (proc(users_online , on_users_online, (string)                           )) // substring of username
    (enum(user_enum                     , (elem1, elem2, elem3)              )) // так можно декларить енум <<<<<<<<<<<<<<<<<<<<<
    (code(/*****************************************************************/
          struct user_type {int a, b;};
          user_type ut;
          void set_user_type(const user_type &r) {ut=r;}
          const user_type& get() const {return ut;}
          /*****************************************************************/)) // так можно вписывать любой пользовательский код <<<<<<<<<<<<<<<<<<<<<
    (type(user_struct, (int, a)(long, b)(float, c)                           )) // а вот так юзер может описывать структуры. имея эту информацию, я могу сгенерить код сериализации/десериализации
    ,
    (yarmi),        // server invoker namespace
    server_invoker, // name of the server invoker
    (proc(registration , on_registration, (string, std::string)              )) // message : registration key
    (proc(activation   , on_activation  , (string)                           )) // message
    (proc(login        , on_login       , (string)                           )) // message
    (proc(logout       , on_logout      , (string)                           )) // message
    (proc(users_online , on_users_online, (array<string>)                    )) // vector of usernames
);

(обрати внимание на префиксы 'proc'/'code'/'type')

L>Посмотри доку по thrift. Там есть набор стандартных типов, int-ы, float-ы и тд. Есть массивы и map-ы, помимо этого — можно определять пользовательские структуры и enum-ы.

нашел такое: https://diwakergupta.github.io/thrift-missing-guide/

L>Берешь длинный буфер и вызываешь async_read(buffer, buffer_len, ...) в буффер приходит какое-то количество данных — меньше или равно buffer_len, далее, ты ставишь его в очередь, берешь новый буффер и делаешь с ним то же самое.

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

L>Параллельный поток или пулл потоков вычерпывает буферы из очереди и парсит, парсер должен быть написан с расчетом на то, что в одном буфере может прийти множество сообщений, при чем не целое их количество. Это довольно тривиально, ты берешь первый буфер, он гарантированно начинается с заголовка сообщения (если его размер больше размера этого заголовка), потом смотришь, если длина сообщения меньше чем количество данных, записаных в буффер — то значит сообщение целиком там — десериализуешь его и переходишь к следующему, если сообщение не полностью прочитано (размер сообщения > размер оставшихся в буфере данных), берешь из очереди следующий буфер и тд. Так ты будешь экономить системные вызовы, так как один recv сможет прочитать сразу несколько сообщений.

с этим понятно.

L>Как правило, занимать пулл потоков, обслуживающий ввод-вывод, вычислениями — плохо для производительности. Поэтому вычисления выносят в отедльный поток или отдельный пулл потоков в котором происходит парсинг полученных данных и обработка результатов.

в наших реальных проектах оно так и есть.
т.е. код сервера который ты видишь, мало чем отличается, за исключением того, что этот метод имеет такую сигнатуру: void on_received(std::shared_ptr<char> ptr, const std::size_t size), и внутри зовет logic_ios.post([ptr, size](){...});, где logic_ios — отдельный io_service работающий в отдельном потоке.
но YARMI задумывался как упрощенная альтернатива, позволяющая быстро реализовать RPC клиент-сервер. ну хз, может быть действительно стОит расширить до отдельных потоков для ввода-вывода и логики...

L>Желательно использовать ограниченную очередь, чтобы в том случае, когда обработка у тебя медленнее чем чтение, не сжиралась вся память. Если представить, что с твоим сервером работает один клиент, то все равботает на стороне сервера примерно так: чтение заголовка сообщения — чтение тела сообщения — обработка. Это по сути синхронная работа, несмотря на то, что используется асинхронный апи. Напрочь игнорируется тот факт, что пока ты обрабатываешь сообщение номер N, ты можешь читать сообщение N+1(+2, 3,...). Т.е. ты пишешь сервер без read ahead, хотя read ahead у тебя возможен, так как RPC функции у тебя не могут возвращать результаты, т.е. поток сообщений односторонний.

да, это так. но это было сделано сознательно, ради упрощения...
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[5]: Protoctor - communication protocol constructor
От: Lazin Россия http://evgeny-lazin.blogspot.com
Дата: 14.05.14 13:52
Оценка:
Здравствуйте, niXman, Вы писали:

X>позволить описывать пользовательские данные я могу и средствами препроцессора, используя префикс для каждого элемента описания.

X>например так:
X>
X>YARMI_CONSTRUCT(
X>    (yarmi),        // client invoker namespace
X>    client_invoker, // name of the client invoker
X>    (proc(registration , on_registration, (string)                           )) // username
X>    (proc(activation   , on_activation  , (string, string, string)           )) // registration key : username : password
X>    (proc(login        , on_login       , (string, string)                   )) // username : password
X>    (proc(logout       , on_logout      , ()                                 )) // without args
X>    (proc(users_online , on_users_online, ()                                 )) // without args
X>    (proc(users_online , on_users_online, (string)                           )) // substring of username
X>    (enum(user_enum                     , (elem1, elem2, elem3)              )) // так можно декларить енум <<<<<<<<<<<<<<<<<<<<<
X>    (code(/*****************************************************************/
X>          struct user_type {int a, b;};
X>          user_type ut;
X>          void set_user_type(const user_type &r) {ut=r;}
X>          const user_type& get() const {return ut;}
X>          /*****************************************************************/)) // так можно вписывать любой пользовательский код <<<<<<<<<<<<<<<<<<<<<
X>    (type(user_struct, (int, a)(long, b)(float, c)                           )) // а вот так юзер может описывать структуры. имея эту информацию, я могу сгенерить код сериализации/десериализации
X>    ,
X>    (yarmi),        // server invoker namespace
X>    server_invoker, // name of the server invoker
X>    (proc(registration , on_registration, (string, std::string)              )) // message : registration key
X>    (proc(activation   , on_activation  , (string)                           )) // message
X>    (proc(login        , on_login       , (string)                           )) // message
X>    (proc(logout       , on_logout      , (string)                           )) // message
X>    (proc(users_online , on_users_online, (array<string>)                    )) // vector of usernames
X>);
X>

X>(обрати внимание на префиксы 'proc'/'code'/'type')

Если я по этому описанию захочу сгенерировать клиента для руби, мне все что в code придется переводить на рубе? Дело конечно твое, но я бы предпочел декларативный IDL без кусков кода


L>>Берешь длинный буфер и вызываешь async_read(buffer, buffer_len, ...) в буффер приходит какое-то количество данных — меньше или равно buffer_len, далее, ты ставишь его в очередь, берешь новый буффер и делаешь с ним то же самое.

X>под буфером, подразумевается нечто, оперирующее непрерывным участком памяти? т.е. расширение с хвоста, и усечение с головы?
Я имел ввиду обычный Си-шный массив.
Re[4]: Protoctor - communication protocol constructor
От: Evgeny.Panasyuk Россия  
Дата: 14.05.14 13:59
Оценка:
Здравствуйте, Lazin, Вы писали:

L>Берешь длинный буфер и вызываешь async_read(buffer, buffer_len, ...) в буффер приходит какое-то количество данных — меньше или равно buffer_len, далее, ты ставишь его в очередь, берешь новый буффер и делаешь с ним то же самое. Параллельный поток или пулл потоков вычерпывает буферы из очереди и парсит, [...]

L>Желательно использовать ограниченную очередь, чтобы в том случае, когда обработка у тебя медленнее чем чтение, не сжиралась вся память.

Тогда уже проще взять circular buffer.
Re[6]: Protoctor - communication protocol constructor
От: niXman Ниоткуда https://github.com/niXman
Дата: 14.05.14 14:04
Оценка:
Здравствуйте, Lazin, Вы писали:

L>Если я по этому описанию захочу сгенерировать клиента для руби, мне все что в code придется переводить на рубе? Дело конечно твое, но я бы предпочел декларативный IDL без кусков кода

'code' приводился как вариант того, что можно сделать. 'code' вообще наверное следует исключить из описания...

L>>>Берешь длинный буфер и вызываешь async_read(buffer, buffer_len, ...) в буффер приходит какое-то количество данных — меньше или равно buffer_len, далее, ты ставишь его в очередь, берешь новый буффер и делаешь с ним то же самое.

X>>под буфером, подразумевается нечто, оперирующее непрерывным участком памяти? т.е. расширение с хвоста, и усечение с головы?
L>Я имел ввиду обычный Си-шный массив.
так а как быть, если у меня одно сообщение находится в двух буферах? перед десериализацией детектировать этот факт, создавать новый непрерывный буфер необходимого размера, копировать два тех буфера в него, и потом десериализовать?
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[5]: Protoctor - communication protocol constructor
От: niXman Ниоткуда https://github.com/niXman
Дата: 14.05.14 14:05
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Тогда уже проще взять circular buffer.

ну да, я тоже об этом подумал. просто не понимаю как Lazin это предлагает сделать.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[6]: Protoctor - communication protocol constructor
От: niXman Ниоткуда https://github.com/niXman
Дата: 14.05.14 14:08
Оценка:
Здравствуйте, niXman, Вы писали:

X>ну да, я тоже об этом подумал. просто не понимаю как Lazin это предлагает сделать.

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

при использовании очереди буферов, можно реализовать "заточенный" итератор, который сам быдет ходить по буферам при чтении через него данных...
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[7]: Protoctor - communication protocol constructor
От: Lazin Россия http://evgeny-lazin.blogspot.com
Дата: 14.05.14 14:23
Оценка:
Здравствуйте, niXman, Вы писали:

X>Здравствуйте, niXman, Вы писали:


X>>ну да, я тоже об этом подумал. просто не понимаю как Lazin это предлагает сделать.

X>он, похоже, предлагает использовать очередь буферов. но тогда как быть с ситуацией, когда одно сообщение располагается в нескольких буферах? так как я предположил? — помоему, это как-то оверхедно.

Можно использовать большой циклический буфер, как предложили выше, это сделает код проще. Если одно сообщение размазано по нескольким буферам, код парсера входных данных должен это предусматривать и, например, копировать все это в один непрерывный участок памяти а потом обрабатывать, либо что-нибудь еще. В общем, поэтому это сложнее циклического буфера. Преимущество перед циклическим буфером в том, что буферы можно создавать по требованию и если нагрузка небольшая, то будет достаточно одного-двух буферов, а циклический буфер нужно создать заранее нужного размера, который рассчитан на худший случай. К тому же, эти один-два буфера могут быть горячими в кэше, а циклический буфер последовательно переписывается весь.
И непонятно, почему сообщение должно быть размазано на несколько буферов, оно может быть неограниченной длины? Ты же ограничиваешь максимальный размер сообщения каким-либо образом, или просто рассчитываешь на то, что никто не сформирует сообщение, которое сможет положить твой сервер?
Re[8]: Protoctor - communication protocol constructor
От: niXman Ниоткуда https://github.com/niXman
Дата: 14.05.14 14:48
Оценка:
Здравствуйте, Lazin, Вы писали:

L>Можно использовать большой циклический буфер, как предложили выше, это сделает код проще. Если одно сообщение размазано по нескольким буферам, код парсера входных данных должен это предусматривать и, например, копировать все это в один непрерывный участок памяти а потом обрабатывать, либо что-нибудь еще. В общем, поэтому это сложнее циклического буфера. Преимущество перед циклическим буфером в том, что буферы можно создавать по требованию и если нагрузка небольшая, то будет достаточно одного-двух буферов, а циклический буфер нужно создать заранее нужного размера, который рассчитан на худший случай. К тому же, эти один-два буфера могут быть горячими в кэше, а циклический буфер последовательно переписывается весь.

при использовании циклического буфера, насколько я представляю его устройство — он внутрях будет постоянно перемещать данные при усечении с головы? (использовал циклический буфер для элементов некоторых типов — тут все понятно. но вот использование циклического буфера для байт — помоему дикий оверхед как по памяти, так и по алгоритмической сложности. это почти похоже по безумству на использованию std::list<char> для массивов)

L>И непонятно, почему сообщение должно быть размазано на несколько буферов, оно может быть неограниченной длины?

неограниченной длины сообщение быть не может.
но юзер же может передавать сообщения размером как 1 байт, так и несколько(десятков? сотен?) мегабайт. не запретишь же ему...

если же у нас используется очередь буферов, — тогда ситуация когда сообщение разбито на несколько буферов(которые, к тому же, могут быть частично заполнеными) — вполне реальна. даже, думается мне, — стандартна.

L>Ты же ограничиваешь максимальный размер сообщения каким-либо образом, или просто рассчитываешь на то, что никто не сформирует сообщение, которое сможет положить твой сервер?

на самом деле, огранияение сообщения есть — 4Гб(ибо размер тела записывается в std::uint32_t) =)
да, это не предусмотрено. да и сложно это предусмотреть, ибо зависит от задачи в которой применяется этот сервер. ну, как вариант, можно в конструктор сервера передавать значение максимального размера сообщения. сделаю.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[9]: Protoctor - communication protocol constructor
От: Evgeny.Panasyuk Россия  
Дата: 14.05.14 15:10
Оценка:
Здравствуйте, niXman, Вы писали:

X>при использовании циклического буфера, насколько я представляю его устройство — он внутрях будет постоянно перемещать данные при усечении с головы?


Нет, там просто сдвигаются указатели.
Например:
//----************----//
      ^ first     ^last
или
//***--------*********//
     ^last   ^ first
Re[8]: Protoctor - communication protocol constructor
От: Evgeny.Panasyuk Россия  
Дата: 14.05.14 15:21
Оценка:
Здравствуйте, Lazin, Вы писали:

L>Можно использовать большой циклический буфер, как предложили выше, это сделает код проще. Если одно сообщение размазано по нескольким буферам, код парсера входных данных должен это предусматривать и, например, копировать все это в один непрерывный участок памяти а потом обрабатывать, либо что-нибудь еще. В общем, поэтому это сложнее циклического буфера.


В циклическом буфере тоже нужно предусматривать подобную ситуацию — при переходе через конец в начало.

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


Capacity циклического буфера можно изменять в соответствии с необходимыми условиями (например, это есть в boost::circular_buffer).

L>К тому же, эти один-два буфера могут быть горячими в кэше, а циклический буфер последовательно переписывается весь.


Да, аргумент. В случае с циклическим буфером придётся его специально уменьшать при снижении нагрузки (или что-нибудь подобное), а в случае с очередью достаточно просто использовать LIFO стэк для чистых буферов.
Re[9]: Protoctor - communication protocol constructor
От: Lazin Россия http://evgeny-lazin.blogspot.com
Дата: 14.05.14 15:23
Оценка:
Здравствуйте, niXman, Вы писали:

X>при использовании циклического буфера, насколько я представляю его устройство — он внутрях будет постоянно перемещать данные при усечении с головы? (использовал циклический буфер для элементов некоторых типов — тут все понятно. но вот использование циклического буфера для байт — помоему дикий оверхед как по памяти, так и по алгоритмической сложности. это почти похоже по безумству на использованию std::list<char> для массивов)

циклический буфер это просто массив + пара индексов, на начало данных и на конец, найди какую-нибудь реализацию и посмотри, это правда очень просто

L>>И непонятно, почему сообщение должно быть размазано на несколько буферов, оно может быть неограниченной длины?

X>неограниченной длины сообщение быть не может.
X>но юзер же может передавать сообщения размером как 1 байт, так и несколько(десятков? сотен?) мегабайт. не запретишь же ему...
т.е. безопасность при проектировании не бралась в расчет

X>если же у нас используется очередь буферов, — тогда ситуация когда сообщение разбито на несколько буферов(которые, к тому же, могут быть частично заполнеными) — вполне реальна. даже, думается мне, — стандартна.

обычно стараются подобрать размер буфера таким образом, чтобы средний размер сообщения был сильно меньше размера буфера

L>>Ты же ограничиваешь максимальный размер сообщения каким-либо образом, или просто рассчитываешь на то, что никто не сформирует сообщение, которое сможет положить твой сервер?

X>на самом деле, огранияение сообщения есть — 4Гб(ибо размер тела записывается в std::uint32_t) =)
X>да, это не предусмотрено. да и сложно это предусмотреть, ибо зависит от задачи в которой применяется этот сервер. ну, как вариант, можно в конструктор сервера передавать значение максимального размера сообщения. сделаю.
Если у тебя макс. размер сообщения = 4Гб, то я могу легко положить сервер создав множество подключений к нему и начав передавать туда сообщения размером 0xFFFFFFFF, твой сервер попытается алоцировать гигабайты памяти и словит bad_alloc. Мало того, это ограничение должно существовать не только на уровне всего сообщения но и на уровне десериализатора. Ты же сериализуешь всякие строи и массивы? Значит у каждой строки и каждого массива есть длина и код десериализатора должен обрабатывать как-то случаи, когда там очень большое число внутри. Помимо этого, возможны ситуации, когда ты сериализуешь какую-нибудь иерархическую структуру данных (например структура, состоящая из других структур) и десерализатор в цикле вытаскивает рекурсивно, либо последовательно все данные, при этом цикл или рекурсия управляются данными, пришедшими по сети. В этом случае, как правило ограничивают максимальную глубину рекурсии, либо максимальное коилчество итераций, чтобы злоумышленник не смог создать сообщение, которое заставит десериализатор крутиться в бесконечном цикле, либо уронить приложение по stack overflow. В общем, это сложная тема. Если твой сервер должен уметь торчать в интернет — нужно такие вещи обрабатывать.
Re[9]: Protoctor - communication protocol constructor
От: Lazin Россия http://evgeny-lazin.blogspot.com
Дата: 14.05.14 15:35
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>В циклическом буфере тоже нужно предусматривать подобную ситуацию — при переходе через конец в начало.

точно, я как-то не подумал об этом

EP>Capacity циклического буфера можно изменять в соответствии с необходимыми условиями (например, это есть в boost::circular_buffer).

Придется лочить весь буфер и копировать его содержимое. Не самый оптимальный вариант в случае, если у тебя вдруг резко возросла нагрузка на сервер и потребовалось больше памяти.
Думаю можно придумать гибридный вариант: когда у тебя забивается циклический буфер маленького размера, ты не увеличиваешь его, а создаешь буфер большего размера (х2) и продолжаешь писать в него. Код, который вычерпывает данные из буфера сначала вычерпывает их из маленького буфера а затем удаляет его и принимается за большой. Ну и это можно продолжать до тех пор, пока буфер не вырастет до макс. размера, заданного в конфигурации сервера. В этом случае, заполнение буфера должно создавать back-pressure на того, кто пишет новые данные в буфер.
Re[10]: Protoctor - communication protocol constructor
От: Evgeny.Panasyuk Россия  
Дата: 14.05.14 15:37
Оценка:
Здравствуйте, Lazin, Вы писали:

L>Если у тебя макс. размер сообщения = 4Гб, то я могу легко положить сервер создав множество подключений к нему и начав передавать туда сообщения размером 0xFFFFFFFF, твой сервер попытается алоцировать гигабайты памяти и словит bad_alloc.


Частично это обходится с помощью асинхронной сериализации
Автор: Evgeny.Panasyuk
Дата: 27.12.13
. То есть как обходится — нужно будет передавать не просто большой размер в заголовке, но ещё и сами байтики.

P.S. А вообще да, максимальные размеры сообщений и полей обычно ограничиваются в спецификации протокола.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.