LINUX.ORG.RU

boost::asio::serial_port - асинхронное чтение с ограничением по таймеру

 , , ,


1

2

Всем привет. Решил тут поковырятся в крестах на злобу дня. Но нигде не могу найти правильный рецепт приготовления. Есть грамотные кто может подсказать по сабжу?

★★★★★

Последнее исправление: yax123 (всего исправлений: 1)

Может озвучить конкретную задачу/проблему? Вдруг и малограмотным будет интересно. Или по крайней мере, для будущего будет понятно, что 22 декабря 2020 экспертов по вопросу на ЛОРе не было.

seiken ★★★★★
()

Т.е. ты запускаешь асинхронное чтение и хочешь таймаут задействовать? Что конкретно ты хочешь выполнить при достижении таймаута?

Допустим ты хочешь отменить чтение, если таймаут достигнут, тогда в калбэке таймаута вызови у сокета функцию cancel() или останови таймер в калбэке для чтения.

Есть deadline_timer.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 2)

Ммм, чет ты хрень какую-то загадал?

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

Если так, то на вскидку тебе нужен либо флаг за локом и deadline_timer который ты взводишь до начала ожидания чтения. Или, если тебе интересно в конкретной точке подождать - то хватит флага за локом и условной переменной(но, тогда вообще не понятно, зачем асинхронность).

Хотя, может и готового чего есть…

pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 1)

Или, ты хочешь всё же прервать операцию чтения, если она подвисла?

Тогда - man cancelation points.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

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

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

А, забей, тупая идея, флаг на куче придётся создавать и вообще костыльно.

Нормально будет завести второй сервис чисто под это чтение и использовать run_for. Тогда флаг было ли чтение можно копировать без проблем с синхронизацией.

pon4ik ★★★★★
()
Ответ на: комментарий от ox55ff

А мне тоже в голову пришёл этот API в первую очередь, но - как отменить зашедуленое чтение то?

pon4ik ★★★★★
()
Ответ на: комментарий от ox55ff

А, ы, можно deadline_timer + cancel. Но - непонятно как в хэндлере таймера проверять, что чтение началось например. Так можно и данные потерять же? По-крайней мере если цикл крутит больше одного потока.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Похоже, зависит от того однопоточный ли цикл или нет. Если однопоточный - то можно использовать deadline_timer + cancel. Если многопоточный то run_for(по факту прямой аналог pselect в данном случае).

pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от seiken

Судя по ответам, спецов по асио тут нет.

А хочу я вот чего.

По сериалу валяться пачки байт, от 80 до 500 байт примерно. Период 60 мс. Мне нужно без потерь выгребать весь поток. При этом не лочится на чтении, чтобы во время отловить, что очередной пакет не пришел.

Действую так. Делаю буфер н байт. Таймаут на н байт плюс пару байт. Запускаю асинк_вайт с калбеком. Потом асинк_рид с калбеком и буфером. Потом в цикле кручу ран_уан (извините за мой мутковский) и слежу чего там калбеки получат и через член класса передадут в цикель с ран_уан.

Если данных нет, вскакивает таймер, ставит статус таймаут, а я потом в цикле это перехватываю и отменяю чтение. Тут есть два варианта, что-то успел прочитать или нет. Если нет - то выходим и опять вызываем функцию чтения со взводом таймера и т д. А вот если что-то успели прочитать, тут уже варианты.

  1. Есть 485-усб на базе ftdi232rl. Там, после частичного чтения, если обратно начать читать, то данные не теряются.
  2. Есть в паралель моха 1150 из которой можно прочитать пяток байт, выскочить по таймауту, а потом заскочить, а в буфере уже байт ~10 примерно потеряно.

И это все при одинаковом коде и одинаковом источнике данных. При этом аналогичный код на сишечке отлично работает уже год. Причем с обоих уартов и нативного ттуС0.

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

При этом играясь размером буфера и таймаутом можно добиться чистого приема из мохи, но значения там весьма большие. Не хочется такие ворота ставить на период передачи пакета.

Драйвер на моху официальный.

Как-то так.

yax123 ★★★★★
() автор топика
Последнее исправление: yax123 (всего исправлений: 1)
Ответ на: комментарий от yax123

На самом деле, там все относительно просто, единственный затык, который может возникнуть это с pool и run (первый можно запустить в отдельном потоке и необходимо дергать постоянно, второй нельзя запустить в отдельном потоке и он блокируется пока все задачи не закончаться) и еще если задачи кончились (произошло разьединение), последующая работа не возможно без reset контекста. В остальном берешь их пример с асинхронным клиентом и меняешь сокет на serial_port.

Silerus ★★★★
()
Ответ на: комментарий от Silerus

В остальном берешь их пример с асинхронным клиентом и меняешь сокет на serial_port.

Лично вы пробовали так делать? У вас получилось?

Пока, что я вижу что это точно не так.

yax123 ★★★★★
() автор топика
Ответ на: комментарий от yax123

Получилось, но трахался прилично, пока не понял те нюансы, что озвучил. Но если вам надо попроще возьмите Qt sdk.

Silerus ★★★★
()
Ответ на: комментарий от yax123

Судя по ответам, спецов по асио тут нет.

Вообще-то я тебе выше привел рабочую схему. Можно придумывать более сложный вариант, или реализовать по-другому, но она рабочая.

rumgot ★★★★★
()
Ответ на: комментарий от Silerus

Получилось, но трахался прилично, пока не понял те нюансы, что озвучил.

Кусок кода с установкой таймера, запуском чтения и ожиданием run можно посмотреть?

Если трахались порядочно, тогда такой вопрос:

m_timer.async_wait( ... timeout_expired ..)
async_read(... read_completed ..)

while(true)
{
...
  std::size_t num = m_io.run_one(err);
...
}

Что будет происходить с данными в с самого начала? Например. Все это запустил. Таймер еще не окончился. Рассмотрим 2 варианта.

  1. Данных на входе нет. Условия вызова read_completed? Условие выхода из run_one?

  2. Пришло пара байт Что произойдет?

yax123 ★★★★★
() автор топика
Ответ на: комментарий от rumgot

Вообще-то я тебе выше привел рабочую схему

Это очевидное решение, которое работает не так хорошо как кажется. На мутковском я это описал (не удобно с телефона, поэтому вот так).

yax123 ★★★★★
() автор топика
Ответ на: комментарий от yax123

Ну дык логично, что при прерывании чтения возможна потеря байт. С моей позиции прерывание - это на крайний случай, нештатный, когда тебе целенаправленно нужно переначать процесс снова. Но не для постоянного дерганья в процессе одной сессии чтения. С другой стороны я мне в принципе не не нравится твоя архитектура: у тебя посылки (пачки) переменной длины и разделяются только временными интервалами. Должны быть маркеры начала/конца и в идеале контрольная сумма. Ладно когда есть реалтайм операционка, но на операционке общего назначения (это я говорю про ту часть твоей системы, где выполняется обсуждаемый код) я бы не строил систему основываясь на таймерах.

rumgot ★★★★★
()
Ответ на: комментарий от yax123

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

rumgot ★★★★★
()
Ответ на: комментарий от yax123

у меня не run, а pool, мне еще графический интерфейс надо крутить, а они как бы ссорятся между собой. Вот так у меня крутится контекст

    IOContextWorking.store(true);
      IOContextStopped.store(false);
      std::error_code ec;
      while (IOContextWorking.load()) {
	  Context.poll(ec);
	  if (ec) {
	    std::cerr << "Ошибка " << ec.message() << std::endl;
	  }
	
        std::this_thread::sleep_for(milliseconds(EventTimeOut));
      }
      IOContextStopped.store(true);
    }));

, а вот стартует read:

  DeadLine.expires_after(milliseconds(ReadTimeOut));
  asio::async_read_until(
      Socket, asio::dynamic_buffer(InputBuffer), Delimiter,
      [this](const std::error_code &Error, size_t n) { HandleRead(Error, n); });

И в коллбеке эта функция повторно вызывается. Теперь по поводу вашего кода. read_one заблокируется и будет ждать любого первого выполненного задания контекста, причем не важно какого, когда задание будет выполнено - он дернет соответствующий коллбек и выполнение вашего цикла продолжиться. Если пришли два байта и эти два байта соответствуют условиям вашего async_read/_until (буфер заполниться или символ конца чтение и т.д) - то будет вызван коллбек, если буфер устройствам вами был прочитан не полностью - то остаток, останется висеть в буфере и будет ждать следующего read.

Silerus ★★★★
()
Ответ на: комментарий от rumgot

в идеале сериал, без слоя сверху, кусок говна, но кому нужна архитектура да?

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

anonymous
()
Ответ на: комментарий от anonymous

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

Так то да, но у ТС изначально условие: нужно разделять байты на пачки по признаку временной разницы 60мс.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)
Ответ на: комментарий от rumgot

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

anonymous
()
Ответ на: комментарий от rumgot

Ну дык логично, что при прерывании чтения возможна потеря байт

Я это не написал в последний раз подробно, но в ночном посте упомянул. Я не делаю в timeout_expired прямого вызова ::serial_port.cancel() Это происходит после выхода из run_one и проверки флага, что таймер сработал. По идее никаких потерь не должно быть.

Должны быть маркеры начала/конца и в идеале контрольная сумма. Это все есть. После выгребания байт они уходят в «очередь» где специальный компонент, ищет начало, проверяет заголовок, CRC и общую структуру пакета. Достает полезную нагрузку и отправляет дальше по конвееру.

Для меня важно вычитывать небольшими порциями (типа 16 байт). При этом не лочится при отсутствии данных (между посылками). И отслеживать случай когда очередная посылка не пришла вовремя (это повод считать, что посыльщик решил пофилонить и его нужно взбодрить живительным ресетом). Ну и отдельная история когда сам порт может внезапно исчезнуть (это когда usb-485 из порта вываливается, но эта другая история).

yax123 ★★★★★
() автор топика
Ответ на: комментарий от rumgot

читай каждый по одному байту и в калбэке чтения проверяй

Блин, люди писали цельную asio, всякие таймеры-буферы-полинги. И предлагается все это выкинуть, по байту читать и дергая на каждый байт системный таймер смотреть не пора ли нам валить.

yax123 ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Ну честно говоря, я сталкивался с таким: вот железячники сделали прибор и сказали вот прибор, вот протокол, делай софт.

Но у меня была возможность им сказать, чтобы делали нормальный протокол без необходимости обмазываться таймаутами со всех сторон. И частично они это сделали (:-), все же частично пришлось и мне таймауты использовать).

rumgot ★★★★★
()
Ответ на: комментарий от yax123

Блин, люди писали цельную asio, всякие таймеры-буферы-полинги. И предлагается все это выкинуть, по байту читать и дергая на каждый байт системный таймер смотреть не пора ли нам валить.

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

rumgot ★★★★★
()
Ответ на: комментарий от yax123

Блин люди думали старались, целый концепт кипалайв придумали, а ты тут предлагаешь мс считать

anonymous
()
Ответ на: комментарий от rumgot

нужно разделять байты на пачки по признаку временной разницы 60мс.

Я может не корректно выразился. Мне нужно отслеживать, что данных уже более чем 60мс нет.

Рубить непрерывнй поток данных на пакеты - задача давно решенная.

yax123 ★★★★★
() автор топика
Ответ на: комментарий от yax123

ну так и проверяй очередь раз в 60мс, если совсем нет выхода, нахрена в сам порт лазить?

anonymous
()
Ответ на: комментарий от anonymous

целый концепт кипалайв придумали

рукалицо.жпг

Каждый, каждый раз убеждаюсь, что аноним несет херню. Но если в лолксах это еще ладно (мы там все блещем никому не нужно эрудицией), то здесь-то зачем?

Какой кипалайв на последовательном порту? Перестань бредить и вернись в лолксы.

yax123 ★★★★★
() автор топика
Ответ на: комментарий от yax123

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

Тю. Ну так это компьютер будет дёргать, а не ты. Сиди и пей чаёк, а процессор будет шуршать байтиками.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

Тю

иди в след за анонимом

yax123 ★★★★★
() автор топика
Ответ на: комментарий от yax123

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

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

anonymous
()
Ответ на: комментарий от yax123

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

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)
Ответ на: комментарий от rumgot

то ты хочешь отменять чтение? А зачем?

Это концепт. Хочу попробовать не сидеть в локе ожидая данных. Предположим это однопоток, и хочется делать еще что-то кроме ожидания данных которых там нет. То есть логика такая. Я в майнлупе читаю мелкими кусочками, с таймаутом на прием этого куска. За раз вычитываю от 1 до этого куска отдаю дальше. Параллельно смотрю сколько времени мне возвращались нули. Если набралось на период выдачи пакетов, то большой аларм - иду все бодрить.

Конечно можно все в кучу потоков и все такое, но это не спортивно.

yax123 ★★★★★
() автор топика
Ответ на: комментарий от yax123

Хочу попробовать не сидеть в локе ожидая данных

Так при асинхронном чтении какой лок? Просто калбэк не будет вызван и все. При этом все остальные задачи будут работать.

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

ну что ты с козырей сразу, погоди, возможно мы станем свидетелями просветления))

anonymous
()
Ответ на: комментарий от rumgot

какой лок

в run.

Или ты утверждаешь, что он из него будет вываливаться даже если нет данных?

Меня этот скользкий вопрос и интересует.

Похоже я забыл про то, что таймер то тоже вешается на io_service. Таким образом он вывалится из run либо после срабатывания таймера либо срабатывания калбека чтения.

yax123 ★★★★★
() автор топика
Ответ на: комментарий от yax123

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

Silerus ★★★★
()
Ответ на: комментарий от Silerus

run - у тебя заблокирует выполнение остальных задач и будет обслуживать только свою очередь,

ну то есть, если вызвать io_service.run только с serial_port и без подключения таймера. то несмотря на async_read, в вызове run все встанет колом, если данные в порт прилетать не будут?

yax123 ★★★★★
() автор топика
Ответ на: комментарий от yax123

почти, все события которые будут зарегестрированны в контексте работать будут (например события таймера, чтения и записи- все что зарегестрированно в контексте), но все остальное нет именно потому у них run в примерах всегда висит в main на return. Кроме того run породит отдельный thread для своих нужд (это к разговору для однопоточности). Потому если тебе надо именно не блокирующие используй poll, но его надо постоянно дергать, потому как он работает так: он проверяет список заданий, если есть выполненные - он дергает их callback функции и вываливается, если нет - просто вываливается. И да run нельзя запихнуть в отдельный thread, a poll можно.

Silerus ★★★★
()
Ответ на: комментарий от Silerus

Потому если тебе надо именно не блокирующие используй poll

В моем случае можно и run_one использовать. Мне не важно какие событие произойдет чтение или таймаут. А он в любом случае вернется. Похоже тщательней надо обработать вариант выхода с частичным чтением.

yax123 ★★★★★
() автор топика
Ответ на: комментарий от yax123

Или ты утверждаешь, что он из него будет вываливаться даже если нет данных?

Если я правильно понял вопрос, то да. Т.е. все остальные события будут обрабатываться, калбэки вызываться и прочее. А если ты про то, будет ле происходить выход из run() - нет не будет.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)
Ответ на: комментарий от rumgot

Если я правильно понял вопрос

скорей всего нет.

Я про выход из вызова run ил run_one. Выход будет только если произошло что-то на дескрипторе (явно там внутре какой-то select).

yax123 ★★★★★
() автор топика
Ответ на: комментарий от yax123

Да. Я уже там уже дописал. Ну тогда нужно обработку boost::asio событий в отдельном потоке делать. Чтобы их не нужно было прерывать.

rumgot ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.