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) это загромоздит пример.
На деле же, пишутся довольно прозрачные обертки для основных сущностей и код превращается в нечто вроде этого:
Здравствуйте, niXman:
X>выглядит жутко %)
С обертками намного приятнее. Но надо учитывать, что это довольно низкоуровневый функционал. Обычно он так и выглядит
X>скажи, а в доке это где-то описано?
Ну я же это не сам придумал . Где-то есть, правда найти сходу не получилось, но точно есть. Пример, который я дал, компилируемый. Можешь запустить, посмотреть — он прекрасно работает.
Re[9]: Protoctor - communication protocol constructor
не могли бы вы привести пример, как использовать protobuf так, чтоб можно было сериализовать переменные без кодогенерации.
т.е. к примеру я имею такую структуру:
struct type {
int v;
std::string s;
std::pair<std::string, std::map<int, std::string>> m;
};
доку читал, но ничего подобного не обнаружил.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[25]: Protoctor - communication protocol constructor
Я думаю никто не станет пользоваться RPC с таким сложным IDL. Посмотри на то, как это реализовано в Thrift и Protobuf (можно еще на cap'n'proto, очень интересный проект) и начни писать спеку по своему RPC. В процессе написания все станет намного яснее.
X>к тому же, не могу решить некоторую проблему с препроцессором. полагаю, в виду того, что он однопроходный.
В виду того, что препроцессор убог. Ты зря не хочешь делать внешний IDL, для описания интерфейса между клиентом и сервером. Недостаток у этого подхода только один — он требует доп. шага компиляции при изменении в описаниях интерфейсов и типов данных. Зато можно сделать нормальный синтаксис. Если лень писать парсер, можно для начала описать все с помощью json или yaml.
Это описание может быть использовано для того, чтобы сгенерировать структуру Endpoint, описания базового виртуального класса для сервера — Callback и код клиента, причем для разных языков (если есть хорошая спека с описанием форматов). И я все таки за то, чтобы использовать фикс. набор примитивных типов на уровне IDL, а не типы из стд библиотеки С++. Вариант с препроцессором слишком ограничен.
X>Приветствую!
X>Сабж^
X>Дока находится на гитхабе, как и сам проект. И, чтоб не синхронизировать постоянные изменения, сюда я доку постить не стану.
X>Собственно — Protoctor
X>Хотелось бы получить пожелания/идеи/рекомендации/фидбэк.
Здравствуйте, Danchik, Вы писали:
D>Чем оно лучше этого? http://thrift.apache.org/
тем же, чем оно лучше protobuf:
1. нет никакого отдельно придуманного синтаксиса для описания сообщений.
2. нет отдельной стадии кодогенерации.
ну, возможно, и еще чем-то лучше. я не в вникал ни в protobuf, ни в thrift.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[3]: Protoctor - communication protocol constructor
Как раз благодаря наличию отдельной стадии генерации thrifft и protobuf позволяют писать программы на разных языках.
Если планируется чисто С++ , тогда конечно это не так удобно как препроцессор, зато в будущем не нужно будет ничего переписывать если другие языки понадобятся.
Здравствуйте, _NN_, Вы писали:
_NN>Хорошо бы сделать сравнение с thrift и protobuf , чтобы действительно понять стоит ли перейти.
я не знаком ни с тем, ни с тем.
опиши задачу, я попробую спрогнозировать резонность перехода
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[3]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>1. нет никакого отдельно придуманного синтаксиса для описания сообщений. X>2. нет отдельной стадии кодогенерации.
3. самый быстрый инвокинг из всех, что я когда либо видел.
если посмотреть на пример сгенерированного, то становится ясно, что для инвокинга используется всего две инструкции cmp и две jmp
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[4]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>Здравствуйте, _NN_, Вы писали:
_NN>>Хорошо бы сделать сравнение с thrift и protobuf , чтобы действительно понять стоит ли перейти. X>я не знаком ни с тем, ни с тем.
X>опиши задачу, я попробую спрогнозировать резонность перехода
Сейчас как бы не актуально.
Я в свое время итоге выбрал protobuf , потому что thrift требует boost-а (небольшие организационные проблемы его внести).
Передаю данные из .NET в C++ , все работает замечательно.
Посему protoctor изначально бы не подошел.
Здравствуйте, niXman, Вы писали:
X>Здравствуйте, _NN_, Вы писали:
_NN>>Посему protoctor изначально бы не подошел. X>простите, но я как-то совсем не уловил, из чего это следует?
А как в .NET создавать ту же структуру данных ? Ручками ?
Здравствуйте, _NN_, Вы писали:
_NN>А как в .NET создавать ту же структуру данных ?
ааа ,вот о чем речь...
_NN>Ручками ?
ну...как вариант =)
структура пакета на самом деле очень проста.
1. один байт — CALL_ID
2. один байт — VERSION_ID
3. четыре байта — BODY_SIZE
4. бади, собственной персоной
=)
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[8]: OFF: Protoctor - communication protocol constructor
Здравствуйте, _NN_, Вы писали:
_NN>Сейчас как бы не актуально. _NN>Я в свое время итоге выбрал protobuf , потому что thrift требует boost-а (небольшие организационные проблемы его внести).
Ну протобуф в отношении количества понимаемых типов из коробки — вроде бы довольно аскетичен? Или я ошибаюсь?
Я в принципе по необходимости рисовал генератор сам (правда для дотнета, но это ж дело техники), ибо работать приходилось со структурами сделанными с фантазией! (микс из порядка байт, представление дат — тоже разное, сложные типы данных типа словарей (map и multimap), блоки данных с CRC, строки фиксированной ширины дополненные пробелами и/или нулями, ну и вышак — определние типа структуры через лукэхид). Это своего рода такой кошмарчик, который удалось систематизировать. После этого, в последний раз когда я смотрел на протобуф — выглядит игрушкой. Ну хотя в нём мне в принципе всё нравится. А возможностей thrift — не знаю. Узнал о нём гораздо позже, смысла даже смотреть не было.
Re[9]: OFF: Protoctor - communication protocol constructor
Здравствуйте, fddima, Вы писали:
F>Здравствуйте, _NN_, Вы писали:
_NN>>Сейчас как бы не актуально. _NN>>Я в свое время итоге выбрал protobuf , потому что thrift требует boost-а (небольшие организационные проблемы его внести). F> Ну протобуф в отношении количества понимаемых типов из коробки — вроде бы довольно аскетичен? Или я ошибаюсь?
Это да, но не смертельно. Можно легко заменить.
Thrift намного более продвинутый в этом плане, поэтому если есть возможность лучше брать его.
F> Я в принципе по необходимости рисовал генератор сам (правда для дотнета, но это ж дело техники), ибо работать приходилось со структурами сделанными с фантазией! (микс из порядка байт, представление дат — тоже разное, сложные типы данных типа словарей (map и multimap), блоки данных с CRC, строки фиксированной ширины дополненные пробелами и/или нулями, ну и вышак — определние типа структуры через лукэхид). Это своего рода такой кошмарчик, который удалось систематизировать. После этого, в последний раз когда я смотрел на протобуф — выглядит игрушкой.
В случае когда формат задан, то конечно тут нужен конструктор форматов , типа: Python Construct.
Но это совсем другая тема
F>Ну хотя в нём мне в принципе всё нравится. А возможностей thrift — не знаю. Узнал о нём гораздо позже, смысла даже смотреть не было.
Здравствуйте, wander, Вы писали:
W>У protobuf есть возможность обойтись без генерации кода. Вся нужная метаинформация доступна в рантайме.
т.е. его кодогенератор не генерит код?
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[8]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>Здравствуйте, wander, Вы писали:
W>>У protobuf есть возможность обойтись без генерации кода. Вся нужная метаинформация доступна в рантайме. X>т.е. его кодогенератор не генерит код?
Есть два способа. С генерацией и без.
Re[11]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>Хотелось бы получить пожелания/идеи/рекомендации/фидбэк.
1. Можно сделать что-то вроде IDL, который не будет требовать отдельного шага кодогенерации в С++, но позволит написать генераторы кода для других языков. Выносим описания в отдельный файл с расширением .yarmi и говорим что это такой IDL, в с++ коде подрубаем этот файл обычным include-ом и обрабатываем препроцессором. Ну и нужно ограничить и спрятать использование типов С++ в IDL. В общем, вместо вот этого — https://github.com/niXman/yarmi/blob/master/examples/chat/protocol.hpp будет что-то такое:
2. Написать нормальный серверный код. Во первых, не нужно читать заголовок и тело сообщения отдельными вызовами, нужно читать сколько пришло а затем анализировать — можно ли прочитать сообщение или сообщение еще не полностью получено. Во вторых — не нужно выполнять пользовательский код внутри io_service-а. У тебя там сейчас все синхронно работает, несмотря на использование асинхронного api. Пока работает пользовательский код, можно начинать читать из сокета следующее сообщение, не дожидаясь окончания обработки. У тебя же чтение сокета и обработка данных, полученных из сокета выполняются последовательно, даже несмотря на то, что у тебя протокол симплексный.
Re[2]: Protoctor - communication protocol constructor
давно не заходил сюда, ибо обычно тут мертво
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
Здравствуйте, 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
Здравствуйте, 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
Здравствуйте, 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
Здравствуйте, Lazin, Вы писали:
L>Берешь длинный буфер и вызываешь async_read(buffer, buffer_len, ...) в буффер приходит какое-то количество данных — меньше или равно buffer_len, далее, ты ставишь его в очередь, берешь новый буффер и делаешь с ним то же самое. Параллельный поток или пулл потоков вычерпывает буферы из очереди и парсит, [...] L>Желательно использовать ограниченную очередь, чтобы в том случае, когда обработка у тебя медленнее чем чтение, не сжиралась вся память.
Тогда уже проще взять circular buffer.
Re[6]: Protoctor - communication protocol constructor
Здравствуйте, 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
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Тогда уже проще взять circular buffer.
ну да, я тоже об этом подумал. просто не понимаю как Lazin это предлагает сделать.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[6]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>ну да, я тоже об этом подумал. просто не понимаю как Lazin это предлагает сделать.
он, похоже, предлагает использовать очередь буферов. но тогда как быть с ситуацией, когда одно сообщение располагается в нескольких буферах? так как я предположил? — помоему, это как-то оверхедно.
при использовании очереди буферов, можно реализовать "заточенный" итератор, который сам быдет ходить по буферам при чтении через него данных...
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[7]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>Здравствуйте, niXman, Вы писали:
X>>ну да, я тоже об этом подумал. просто не понимаю как Lazin это предлагает сделать. X>он, похоже, предлагает использовать очередь буферов. но тогда как быть с ситуацией, когда одно сообщение располагается в нескольких буферах? так как я предположил? — помоему, это как-то оверхедно.
Можно использовать большой циклический буфер, как предложили выше, это сделает код проще. Если одно сообщение размазано по нескольким буферам, код парсера входных данных должен это предусматривать и, например, копировать все это в один непрерывный участок памяти а потом обрабатывать, либо что-нибудь еще. В общем, поэтому это сложнее циклического буфера. Преимущество перед циклическим буфером в том, что буферы можно создавать по требованию и если нагрузка небольшая, то будет достаточно одного-двух буферов, а циклический буфер нужно создать заранее нужного размера, который рассчитан на худший случай. К тому же, эти один-два буфера могут быть горячими в кэше, а циклический буфер последовательно переписывается весь.
И непонятно, почему сообщение должно быть размазано на несколько буферов, оно может быть неограниченной длины? Ты же ограничиваешь максимальный размер сообщения каким-либо образом, или просто рассчитываешь на то, что никто не сформирует сообщение, которое сможет положить твой сервер?
Re[8]: Protoctor - communication protocol constructor
Здравствуйте, Lazin, Вы писали:
L>Можно использовать большой циклический буфер, как предложили выше, это сделает код проще. Если одно сообщение размазано по нескольким буферам, код парсера входных данных должен это предусматривать и, например, копировать все это в один непрерывный участок памяти а потом обрабатывать, либо что-нибудь еще. В общем, поэтому это сложнее циклического буфера. Преимущество перед циклическим буфером в том, что буферы можно создавать по требованию и если нагрузка небольшая, то будет достаточно одного-двух буферов, а циклический буфер нужно создать заранее нужного размера, который рассчитан на худший случай. К тому же, эти один-два буфера могут быть горячими в кэше, а циклический буфер последовательно переписывается весь.
при использовании циклического буфера, насколько я представляю его устройство — он внутрях будет постоянно перемещать данные при усечении с головы? (использовал циклический буфер для элементов некоторых типов — тут все понятно. но вот использование циклического буфера для байт — помоему дикий оверхед как по памяти, так и по алгоритмической сложности. это почти похоже по безумству на использованию std::list<char> для массивов)
L>И непонятно, почему сообщение должно быть размазано на несколько буферов, оно может быть неограниченной длины?
неограниченной длины сообщение быть не может.
но юзер же может передавать сообщения размером как 1 байт, так и несколько(десятков? сотен?) мегабайт. не запретишь же ему...
если же у нас используется очередь буферов, — тогда ситуация когда сообщение разбито на несколько буферов(которые, к тому же, могут быть частично заполнеными) — вполне реальна. даже, думается мне, — стандартна.
L>Ты же ограничиваешь максимальный размер сообщения каким-либо образом, или просто рассчитываешь на то, что никто не сформирует сообщение, которое сможет положить твой сервер?
на самом деле, огранияение сообщения есть — 4Гб(ибо размер тела записывается в std::uint32_t) =)
да, это не предусмотрено. да и сложно это предусмотреть, ибо зависит от задачи в которой применяется этот сервер. ну, как вариант, можно в конструктор сервера передавать значение максимального размера сообщения. сделаю.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[9]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>при использовании циклического буфера, насколько я представляю его устройство — он внутрях будет постоянно перемещать данные при усечении с головы?
Нет, там просто сдвигаются указатели.
Например:
//----************----//
^ first ^last
или
//***--------*********//
^last ^ first
Re[8]: Protoctor - communication protocol constructor
Здравствуйте, Lazin, Вы писали:
L>Можно использовать большой циклический буфер, как предложили выше, это сделает код проще. Если одно сообщение размазано по нескольким буферам, код парсера входных данных должен это предусматривать и, например, копировать все это в один непрерывный участок памяти а потом обрабатывать, либо что-нибудь еще. В общем, поэтому это сложнее циклического буфера.
В циклическом буфере тоже нужно предусматривать подобную ситуацию — при переходе через конец в начало.
L>Преимущество перед циклическим буфером в том, что буферы можно создавать по требованию и если нагрузка небольшая, то будет достаточно одного-двух буферов, а циклический буфер нужно создать заранее нужного размера, который рассчитан на худший случай.
Capacity циклического буфера можно изменять в соответствии с необходимыми условиями (например, это есть в boost::circular_buffer).
L>К тому же, эти один-два буфера могут быть горячими в кэше, а циклический буфер последовательно переписывается весь.
Да, аргумент. В случае с циклическим буфером придётся его специально уменьшать при снижении нагрузки (или что-нибудь подобное), а в случае с очередью достаточно просто использовать LIFO стэк для чистых буферов.
Re[9]: Protoctor - communication protocol constructor
Здравствуйте, 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
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>В циклическом буфере тоже нужно предусматривать подобную ситуацию — при переходе через конец в начало.
точно, я как-то не подумал об этом
EP>Capacity циклического буфера можно изменять в соответствии с необходимыми условиями (например, это есть в boost::circular_buffer).
Придется лочить весь буфер и копировать его содержимое. Не самый оптимальный вариант в случае, если у тебя вдруг резко возросла нагрузка на сервер и потребовалось больше памяти.
Думаю можно придумать гибридный вариант: когда у тебя забивается циклический буфер маленького размера, ты не увеличиваешь его, а создаешь буфер большего размера (х2) и продолжаешь писать в него. Код, который вычерпывает данные из буфера сначала вычерпывает их из маленького буфера а затем удаляет его и принимается за большой. Ну и это можно продолжать до тех пор, пока буфер не вырастет до макс. размера, заданного в конфигурации сервера. В этом случае, заполнение буфера должно создавать back-pressure на того, кто пишет новые данные в буфер.
Re[10]: Protoctor - communication protocol constructor
Здравствуйте, Lazin, Вы писали:
L>Если у тебя макс. размер сообщения = 4Гб, то я могу легко положить сервер создав множество подключений к нему и начав передавать туда сообщения размером 0xFFFFFFFF, твой сервер попытается алоцировать гигабайты памяти и словит bad_alloc.
Здравствуйте, Lazin, Вы писали:
EP>>Capacity циклического буфера можно изменять в соответствии с необходимыми условиями (например, это есть в boost::circular_buffer). L>Придется лочить весь буфер и копировать его содержимое. Не самый оптимальный вариант в случае, если у тебя вдруг резко возросла нагрузка на сервер и потребовалось больше памяти.
Да, но в сценарии small_ring_buffer ---high_load---> large_ring_buffer, эта копия незначительна, так как размер данных которые нужно скопировать мал.
L>Думаю можно придумать гибридный вариант: когда у тебя забивается циклический буфер маленького размера, ты не увеличиваешь его, а создаешь буфер большего размера (х2) и продолжаешь писать в него. Код, который вычерпывает данные из буфера сначала вычерпывает их из маленького буфера а затем удаляет его и принимается за большой.
При необходимости, можно поиграться и в такую "змейку".
Re[11]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>размер буферов делаем константным, а кол-во буферов — нет.
кол-во буферов можно ограничить конфигурацией. можно продумать позднее сокращение буферов, например, если какое-то время еспользуется только часть буферов.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[12]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>так а чем плох вариант с FIFO буферов? X>размер буферов делаем константным, а кол-во буферов — нет.
Если в одном буфере одно сообщение — то будет перерасход памяти, так как буферы будут заполнятся не полностью. Дырки между буферами также могут снижать производительность, так как снижают КПД использования cache/memory throughput.
Плюс, нужно как-то нарезать сообщения по буферам. То есть, например, если мы начали считывать в буфер не зная размера сообщения, то мы можем получить несколько сообщений в буфере, причём последнее может быть не целым — придётся копировать его в новый буфер.
Если же буфера заполнять плотно, то будут дополнительные условные проверки при пересечении границ, что также снизит производительность.
Re[13]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
EP>>Плюс, нужно как-то нарезать сообщения по буферам. X>asio сам это делает: async_read_some()
Нарезать сообщения, а не байты.
Например, есть пустой буфер в 1024 байта, размер сообщения неизвестен, но в диапазоне от 32 до 1024 байт. Вызываем async_read_some, и видим что в буфер пришло одно сообщение в 1000 байт, и ещё часть следующего в 24 байта.
Re[15]: Protoctor - communication protocol constructor
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Нарезать сообщения, а не байты. EP>Например, есть пустой буфер в 1024 байта, размер сообщения неизвестен, но в диапазоне от 32 до 1024 байт. Вызываем async_read_some, и видим что в буфер пришло одно сообщение в 1000 байт, и ещё часть следующего в 24 байта.
понял.
ща посмотрю реализацию circular_buffer...
такой еще вопрос.
в этом примере, мы описываем тип используемый в сообщении:
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) )) // так можно декларить енум <<<<<<<<<<<<<<<<<<<<<
[1] (type(user_struct, (int, a)(long, b)(float, c) )) // а вот так юзер может описывать структуры. имея эту информацию, я могу сгенерить код сериализации/десериализации даже препроцессором.
[2] (proc(some_proc , on_some_proc , (user_struct)))
,
(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
);
тут, в точке 1 мы описывает тип, а в точке 2 — используем его.
таким образом, в client_invoker появится такой метоы: void some_proc(const const user_struct &us);
в тоже время, server_invoker ожидает что будет ему предоставлен класс с таким методом: void on_some_proc(const const user_struct &us);
вопрос в том, что если я генерю user_struct в теле client_invoker, то описание этой структуры не будет доступно server_invoker`у. как быть?
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[16]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>вопрос в том, что если я генерю user_struct в теле client_invoker, то описание этой структуры не будет доступно server_invoker`у. как быть?
Если я правильно понял задачу, то вот несколько вариантов:
1. Должно быть место для описания типов доступных и для клиента и для сервера.
2. Внутри макроса YARMI_CONSTRUCT, первым действием сгенерировать код для всех типов, и для сервера и для клиента.
P.S. А зачем описывать два набора процедур/структур, для клиента/сервера? Нельзя ли просто описать сервисы, а уже клиент и сервер будут выбирать, кому что? Или это заточка под распространённый use-case?
Re[17]: Protoctor - communication protocol constructor
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>1. Должно быть место для описания типов доступных и для клиента и для сервера.
можно сгенерить уникальный неймспейс из 'нейсмспейса клиентского инвокера' + 'имени клиентского инвокера' + 'неймспейса серверного инвокера' + 'имени серверного инвокера'.
EP>2. Внутри макроса YARMI_CONSTRUCT, первым действием сгенерировать код для всех типов, и для сервера и для клиента.
в таком случае, да.
EP>P.S. А зачем описывать два набора процедур/структур, для клиента/сервера? Нельзя ли просто описать сервисы, а уже клиент и сервер будут выбирать, кому что? Или это заточка под распространённый use-case?
они же парные. если описывать отдельно, — велика вероятность ошибок.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[18]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>можно сгенерить уникальный неймспейс из 'нейсмспейса клиентского инвокера' + 'имени клиентского инвокера' + 'неймспейса серверного инвокера' + 'имени серверного инвокера'.
по приведенному выше примеру, получится так: namespace yarmi__client_invoker__yarmi__server_invoker {}
ну и типы клиента и сервера генерить в этом неймспейсе.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[18]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
EP>>P.S. А зачем описывать два набора процедур/структур, для клиента/сервера? Нельзя ли просто описать сервисы, а уже клиент и сервер будут выбирать, кому что? Или это заточка под распространённый use-case? X>>они же парные. если описывать отдельно, — велика вероятность ошибок.
Я предлагаю вместо варианта: описание клиентских типов и функций + описание серверных типов и функций
server
{
struct foo //...
ping(foo x);
}
client
{
struct bar // ...
pong(bar x);
}
рассмотреть вариант: описание типов (отдельно) + описание сервисов + конфигурация указывающая какие из сервисов себе берёт клиент, а какие сервер
types
{
struct foo //...
struct bar // ...
}
service FirstService
{
ping(foo x);
}
service SecondService
{
pong(bar x);
}
// ...
// где-то ниже
connection SomeConn
{
client provides SecondService;
server provides FirstService;
}
(псевдокод логической структуры)
Re[19]: Protoctor - communication protocol constructor
Возможно я не так понял что делает yarmi: устанавливается соединение между клиентом и сервером; у сервера есть некоторые методы, которые могут быть вызваны клиентом; и у клиента есть методы, вызов которых может быть инициирован с сервера; YARMI_CONSTRUCT описывает какие методы есть у клиента и у сервера (+ типы). Так?
Re[21]: Protoctor - communication protocol constructor
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Возможно я не так понял что делает yarmi: устанавливается соединение между клиентом и сервером; у сервера есть некоторые методы, которые могут быть вызваны клиентом; и у клиента есть методы, вызов которых может быть инициирован с сервера; YARMI_CONSTRUCT описывает какие методы есть у клиента и у сервера (+ типы). Так?
да. но описание типов пока в перспективе.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[22]: Protoctor - communication protocol constructor
Здравствуйте, niXman, Вы писали:
X>да. но описание типов пока в перспективе.
Вот, как один из вариантов, вместо описания каждого метода у клиента и сервера в YARMI_CONSTRUCT, задать какие сервисы поддерживаются клиентом и сервером, где сервис это набор методов описанный ранее.
Это позволит переиспользовать сервисы. Например, у клиента и сервера должны быть методы ping и foo, отдельно описываем сервис PingFoo (в котором эти два метода), а потом указываем что и клиент и сервер реализуют этот сервис.
Может такой вариант и нецелесообразен — зависит от задач.
Re[23]: Protoctor - communication protocol constructor
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Вот, как один из вариантов, вместо описания каждого метода у клиента и сервера в YARMI_CONSTRUCT, задать какие сервисы поддерживаются клиентом и сервером, где сервис это набор методов описанный ранее. EP>Это позволит переиспользовать сервисы. Например, у клиента и сервера должны быть методы ping и foo, отдельно описываем сервис PingFoo (в котором эти два метода), а потом указываем что и клиент и сервер реализуют этот сервис. EP>Может такой вариант и нецелесообразен — зависит от задач.
вроде понял о чем ты. но, признаюсь, не вижу практического смысла в таком подходе.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[24]: Protoctor - communication protocol constructor