Russian Qt Forum
Апрель 25, 2024, 16:54 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Очередь запросов  (Прочитано 8280 раз)
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 870


Мы должны приносить пользу людям.


Просмотр профиля
« : Ноябрь 29, 2016, 11:06 »

Коллеги, тема заезженная, но я для себя не нашел подходящего решения.
Задача - наблюдение и управление устройствами по протоколу modbus rtu, подключенными к преобразователям интерфейса USB/RS-485 (не суть важно, к контроллерам). Доступ многопользовательский, для этой цели создан многопоточный сервер, выделяющий для каждого подключившегося клиента свои потоки, в которых осуществляется подключение к устройствам. Запросы выполняются в синхронном режиме - подключение, запрос/ответ, отключение. Асинхронный режим также есть, но это тема для другого разговора, сейчас мне нужно решить другую задачу.

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

Все работает нормально, если конкурируют запросы разных пользователей. Циклы ожидания организуются в разных потоках, очередь движется.
Но если один пользователь начинает делать несколько запросов через малый интервал (например, быстро щелкает мышкой по кнопке), то, из-за медленной реакции устройства (а она может быть от 80 мс до 800), его запросы ставятся в очередь. В программе это можно сделать путем быстрого нажатия несколько раз по кнопке Run. В результате все запросы завершаются таймаутом (Waiting result 0 of request ..).

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

Первая мысль - не ставить в очередь запросы одного пользователя, выполняющиеся в одной сессии подключения, а выполнять только первый или, например, последний, остальные игнорировать. Но это костыль.
Есть ли у вас, коллеги, мысли по поводу того, как организовать очередь запросов при синхронной работе клиентов с сервером?
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
lit-uriy
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3880


Просмотр профиля WWW
« Ответ #1 : Декабрь 02, 2016, 11:52 »

Я бы не стал использовать несколько очередей, да ещё и вложенных.
Если проволока (линия связи / транспорт) одна, то и очередь на доступ к проволоке тоже одна.
Записан

Юра.
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 870


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #2 : Декабрь 06, 2016, 10:10 »

Очередь-то, если речь о массиве/списке, одна. При выполнении запроса на сервере выполняется функция с индивидуальными для каждого запроса параметрами. Назначение очереди в том, чтобы отсрочить выполнение этой функции на время, пока не подойдет ее очередь (т.е. пока не закончится предыдущий вызов функции).
Проблема в другом - как организовать ее ожидание. Иного способа, как использование циклы событий для ожидания и оповещения о том, что из этого цикла пора выходить, я не придумал. Поэтому, если в одном потоке подряд несколько раз вызывается функция, циклы ожидания получаются вложенными.
У меня возникает ощущение, что я что-то не понимаю и, соответственно, не то делаю Подмигивающий
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
Bepec
Гость
« Ответ #3 : Декабрь 06, 2016, 11:54 »

Я б сказал вы страдаете фигней...
Потому что у вас получаются асинхронные вызовы с вложенными событийными циклами.
Если вы хотите синхронных вызовов - не используйте сигнал слотовую систему и циклы событий Qt.
Записан
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 870


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #4 : Декабрь 06, 2016, 12:09 »

Я б сказал вы страдаете фигней...
Это да... Все, спасибо, коллеги, тему можно считать закрытой по причине неправильной постановки задачи Подмигивающий
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #5 : Декабрь 06, 2016, 12:40 »

Трудновато понять что происходит
Код
C++ (Qt)
// ожидание запроса в очереди. Выполняется в клиентском потоке
int CThreadSafeQueue::waitForQueue(int requestId, int msecs)
{
   CQueueLoop loop(this, requestId);
   return loop.waitForQueue(msecs);
}
 
Какой-то запрос начал крутить свой loop, но запросы идут и идут, и каждый делает waitForQueue, т.е. запускает свой такой же loop. Выходит вложенность все нарастает (каждый новый loop крутится внутри предыдущего). Или я не так понял?
Записан
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 870


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #6 : Декабрь 06, 2016, 13:55 »

Все так, если запросы в одном потоке, отчего и проблемы.
А если они в разных потоках (от разных клиентов), работает вот это:
Код:
connect(queue, &CThreadSafeQueue::notify, this, &CQueueLoop::notificationSlot, Qt::QueuedConnection);
т.е. при движении очереди происходит выход из цикла в слоте notificationSlot:
Код:
this->exit(1);
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #7 : Декабрь 06, 2016, 14:18 »

Тогда почему не один CQueueLoop (на все запросы одного клиента)? Все данные "когда соскочить" есть, ну может добавить clientID
Записан
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 870


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #8 : Декабрь 06, 2016, 14:47 »

Согласен, но тогда другие сложности. Я сейчас пытаюсь все упростить.
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
lit-uriy
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3880


Просмотр профиля WWW
« Ответ #9 : Декабрь 07, 2016, 09:27 »

Если совсем упрощенно, то у меня так выглядит.

Есть объект "Транспорт" описывающий конкретную проволоку RS-485.
Есть объект "Устройство" описывающий ModBus-устройство.
"Устройства" добавляются в "Транспорт".

В объекте "Транспорт" есть слот "добавитьВОчередь(Устройство, данные)", он дёргается из произвольного потока.
У "Транспорта" есть сигнал "естьОбновление(Устройство"), к которому подключены заинтересованные объекты.
По получению сигнала объекты проверяют новые данные "Устройства", например, если мы включали дискретный выход устройства, то  у него можно спросить состояние этого выхода.

примерно такая реализация
Записан

Юра.
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 870


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #10 : Декабрь 07, 2016, 10:25 »

Слишком упрощенно. "Транспорт" - для последовательности запросов к modbus-устройствам? Если да, то в нормальном состоянии длина очереди - 0?
"Устройство" - для хранения текущего состояния устройств? Тогда почему сигнал испускает "Транспорт"?
Заинтересованные объекты по сигналу тупо получают данные из "Устройство"?
Кто выполняет запросы к modbus-устройствам?
Если поясните, будут другие вопросы.
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
lit-uriy
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3880


Просмотр профиля WWW
« Ответ #11 : Декабрь 07, 2016, 13:09 »

"Транспорт" - для последовательности запросов к modbus-устройствам? Если да, то в нормальном состоянии длина очереди - 0?
Да, сидящим на одной проволоке, т.е. в случае нескольких проволок имеем несколько экземпляров класса "Транспорт".
Максимум - 1, т.к. в любом случае задание помещается в очередь и пока вся транзакция не завершится оно из очереди не удаляется (читай далее).

"Устройство" - для хранения текущего состояния устройств? Тогда почему сигнал испускает "Транспорт"?
Да.
Потому что только "Транспорт" знает когда транзакция завершилась, но устройство тоже посылает сигнал (дубль, следом за транспортом); на практике оказалось, что в одной части программы удобнее работать с транспортом, а в другой с устройством.

Заинтересованные объекты по сигналу тупо получают данные из "Устройство"?
Нет, они только получают уведомление, что в устройстве произошли изменения. Поэтому заинтересованный вынужден анализировать состояние (обычно всё анализировать не надо, надо только дождатся изменения чего-то конкретного). Т.е. подтверждения завершения конкретной транзакции нет (это минус имеющейся реализации).

Кто выполняет запросы к modbus-устройствам?
Транспорт (он владелец объекта QSerialPort).
Он реализует транзакции на шине, но пакеты (Request PDU) для него формирует "Устройство", он передаёт в "Устройство" принятый пакет (Response PDU) и получает назад либо утвердительный ответ либо ошибку. При ошибке работает специальный алгоритм, повторный запрос со счётчиком неудач либо безусловная посылка (заинтерисованным объектам) сигнала ошибки.
Этот алгоритм уникальный для нашего оборудования и мало связан с ModBus-ом.

В текущей реализации ассоциацию адреса устройства на шине ModBus с экзепляром класса "Устройство" хранит "Транспорт", "транспорт" же и посчитывает CRC ну и окончательно формирует Request ADU тоже "Транспорт". Он же и разбирает "Response ADU" и вытаскивает из него PDU.

P.S.
Концепт в принципе хочется переработать, но более "красивого" решения пока не нарисовалось, изобрести ещё один "некрасивый" и лень, и времени нет, и придётся в дальнейшем поддерживать два "некрасивых", что приведёт к каше в голове.
Записан

Юра.
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 870


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #12 : Декабрь 07, 2016, 15:33 »

В общем, понятно, хотя смутило "Максимум - 1". Наверное, минимум? А после завершения транзакции, задание должно удаляться, это я имел в виду, говоря про нормальное состояние (точнее - исходное) с длиной очереди 0.

И не совсем понятен транзакционный механизм.
"Потому что только "Транспорт" знает когда транзакция завершилась".

Транзакция завершается, когда получен ответ. С другой стороны, вы говорите, что
"Т.е. подтверждения завершения конкретной транзакции нет (это минус имеющейся реализации)."

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


Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
lit-uriy
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3880


Просмотр профиля WWW
« Ответ #13 : Декабрь 08, 2016, 06:36 »

А после завершения транзакции, задание должно удаляться, это я имел в виду, говоря про нормальное состояние (точнее - исходное) с длиной очереди 0.
Да именно так.

И не совсем понятен транзакционный механизм.
"Потому что только "Транспорт" знает когда транзакция завершилась".

Транзакция завершается, когда получен ответ. С другой стороны, вы говорите, что
"Т.е. подтверждения завершения конкретной транзакции нет (это минус имеющейся реализации)."

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


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

Чуть подробнее: Некий высокоуровневый код (объект) помещает в транспорт задание передавая указатель на устройство и специфичные для устройства данные (в текущей реализации эта часть сделана не удачно поэтому не буду описывать детали), дак вот с этого момента высокоуровневый объект не сможет получить информацию/подтверждение, что именно его и именно это задание было выполнено.

Транспорт знает завершилась транзакция или нет, т.к. именно он управляет процессами на шине. Как только ответ получен или произошла ошибка он посылает свой сигнал либо о том что в устройстве произошли изменения, либо об ошибке. Т.е. транспорт никак не связывает задания с высокоуровневым кодом.


Дополнение к предыдущему посту с описанием:
Сигнал, посылаемый устройством, который дублирует сигнал транспорта, содержит список изменившихся переменных устройства (аналог тэга в OPC-серверах).
Высокоуровневый код который не следит за сигналами транспорта, а следит за их дублёрами от устройств может фильтровать сигналы, анализируя полученный список переменных. Если изменилась нужная переменная, то как-то реагируем, если нет, то игнорируем сигнал.
« Последнее редактирование: Декабрь 08, 2016, 06:39 от lit-uriy » Записан

Юра.
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #14 : Декабрь 08, 2016, 12:03 »

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

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

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

- сигнал "очередь обновилась" - проверяем мой (клиента) запрос первый - выполняем действия (посылку, удаление) и возвращаемся в свой EventLoop.

- сигнал "таймаут" (не дождались), и далее по задаче
Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.079 секунд. Запросов: 22.