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

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

Страниц: [1]   Вниз
  Печать  
Автор Тема: [РЕШЕНО] QtWebsocket синхронный вызов  (Прочитано 8097 раз)
Nimbus
Гость
« : Июнь 19, 2013, 10:56 »

Ох давно я уже на С++ и Qt ничего не делал...
Внезапно для одного веб-проектика понадобилось реализовать B2B мини десктоп приложения в трее, которое бы коннектилось по вебсокету к Node.js (используется node-модуль ws). Суть в том, чтобы Node.js периодически отправлял уведомления, а приложение нотифицировало пользователя. Нашёл на гитхабе небольшую либу, реализующую данный протокол. Все данные от сервера идут в формате JSON обычной строкой.
Если сильно утрировано без сложноты, то примерный код выглядит так:

Код
C++ (Qt)
class Manager : public QObject {
Q_OBJECT
private:
  QWsSocket *socket;
  QSystemTrayIcon *icon;
 
private slots:
   void messageReceived(QString);
};
 
Manager::Manager() {
 
   socket = new QWsSocket;
   socket->connectToHost("127.0.0.1", 30003);
   icon = new QSystemTrayIcon;
   icon->show();
 
   QObject::connect(socket, SIGNAL(frameReceived(QString)),
                     SLOT(messageReceived(QString)));
}
 
void Manager::messageReceived(QString msg) {
  //Пренебрежём преобразованием из json в сообщение
  icon->showMessage("новое сообщение", msg);
}
 
Всё работает, всё ништяк, данные приходят и появляется уведомление.
Теперь необходимо реализовать некое подобие RPC при определённом действии пользователя. То есть, реализовать синхронные вызовы на клиенте. То есть, отправлять данные в формате JSON, ожидать ответа именно на данный запрос, при этом не терять обычные уведомления.
К каждому запросу клиента прикрепляется некий ID, генерящийся на клиенте и возвращающийся сервером вместе с ответом, так что проблема с идентификацией запрос : ответ частично решена. Остаётся вопрос синхронности, блокирующей главный поток на время обработки сервером запроса.
Банально есть
Код
C++ (Qt)
void Manager::sendMessage(QString msg) {
   socket->write(msg);
}
 
ответ придёт и вызовется слот messageReceived, но вызовется в главном потоке. Так что, мне нельзя организовать цикл while(true) в методе sendMessage после отправки.
Пробовал так:
Код
C++ (Qt)
void Manager::sendMessage(QString msg) {
   socket->write(msg);
   socket->waitForBytesWritten();
   socket->waitForReadyRead();
}
 
Но это всё дело крашится с сегфолтом при вызове waitForReadyRead, а Node.js'у ничего не приходит и происходит сразу дисконнект сокета.
Как решить данную проблему синхронного вызова? Было бы идеально, если бы слот messageReceived вызывался в другом потоке, но QThread::getCurrentThreadId() при отправке и при получений одинаковый. Или может есть более элегантные решения?
« Последнее редактирование: Июнь 21, 2013, 16:56 от JC » Записан
dio
Гость
« Ответ #1 : Июнь 19, 2013, 15:05 »

Организуйте очередь запросов. Не сразу отправляйте запрос, а ставите его в очередь пока не придет ответ на предыдущей запрос. Ответ пришел, отправляйте следующей запрос.
Записан
Nimbus
Гость
« Ответ #2 : Июнь 19, 2013, 15:46 »

Организуйте очередь запросов. Не сразу отправляйте запрос, а ставите его в очередь пока не придет ответ на предыдущей запрос. Ответ пришел, отправляйте следующей запрос.
Идея хорошая, но, поскольку, отправка данных и приём варятся в одном потоке, то как мне банально вызвать блокировку потока (?) при двух последовательных вызовах типа
Код
C++ (Qt)
QJsonDocument res1 = manager->sendMessageSync(msg1);
QJsonDocument res2 = manager->sendMessageSync(msg2);
 
?

P. S. у меня есть подозрение, что эту магию можно кастовать через QEventLoop...
Записан
dio
Гость
« Ответ #3 : Июнь 19, 2013, 17:30 »

Не могу понять, зачем Вам блокировать поток? Если запросы будут отправляться последовательно, то и ответы должны приходить последовательно.
Записан
Nimbus
Гость
« Ответ #4 : Июнь 19, 2013, 17:47 »

Не могу понять, зачем Вам блокировать поток? Если запросы будут отправляться последовательно, то и ответы должны приходить последовательно.
Да, но ведь, помимо ответов могут ещё идти простые уведомления. Вдруг такое уведомление попадёт в промежуток между запросом и ответом. К тому же, спорный вопрос насчёт "последовательные вопросы - последовательные ответы"
Записан
dio
Гость
« Ответ #5 : Июнь 19, 2013, 17:59 »

Уведомления кэшируйте.

Цитировать
К тому же, спорный вопрос насчёт "последовательные вопросы - последовательные ответы"

Если Вы имеете в виду отсутствие ответа и случайные запросы, то эту ситуацию можно обработать.
Записан
Nimbus
Гость
« Ответ #6 : Июнь 19, 2013, 18:42 »

Уведомления кэшируйте.

Цитировать
К тому же, спорный вопрос насчёт "последовательные вопросы - последовательные ответы"

Если Вы имеете в виду отсутствие ответа и случайные запросы, то эту ситуацию можно обработать.

Да про кэширование это и так ясно. Вопрос-то не в этом Улыбающийся
А в том "как добиться работы такого кода: QJsonDocument res1 = manager->sendMessageSync(msg1);, когда внутри метода sendMessageSync вызывается асинхронный socket->write(), но нет ничего что блокировало бы поток чтобы дождаться нужного ответа и вернуть из метода экземпляр QJsonDocument, полученный в ответе в виде JSON'а?"
Записан
Kurles
Бывалый
*****
Offline Offline

Сообщений: 480



Просмотр профиля
« Ответ #7 : Июнь 19, 2013, 18:59 »

Уведомления кэшируйте.

Цитировать
К тому же, спорный вопрос насчёт "последовательные вопросы - последовательные ответы"

Если Вы имеете в виду отсутствие ответа и случайные запросы, то эту ситуацию можно обработать.

Да про кэширование это и так ясно. Вопрос-то не в этом Улыбающийся
А в том "как добиться работы такого кода: QJsonDocument res1 = manager->sendMessageSync(msg1);, когда внутри метода sendMessageSync вызывается асинхронный socket->write(), но нет ничего что блокировало бы поток чтобы дождаться нужного ответа и вернуть из метода экземпляр QJsonDocument, полученный в ответе в виде JSON'а?"
Еще раз - зачем блокировать поток? вызвали write(), завели таймер на время, которого заведомо должно хватить на ожидание ответа, отдали управление евентлупу. Затем в слоте, привязанному к readyRead(), останавливаем таймер, читаем ответ, и если это не уведомление, то переходим к следующему сообщению в очереди, иначе заново заводим таймер и продолжаем ждать ответ. Если слот, привязанный к таймеру успевает выполниться - беда, сервер не отвечает, тоже каким-либо образом эта ситуация обрабатывается. Упрощенно так. Можно еще что-то типа конечного автомата с его состояниями прикрутить.
Записан

Код
C++ (Qt)
while(!asleep()) sheep++;
Nimbus
Гость
« Ответ #8 : Июнь 19, 2013, 19:04 »

Еще раз - зачем блокировать поток? вызвали write(), завели таймер на время, которого заведомо должно хватить на ожидание ответа, отдали управление евентлупу. Затем в слоте, привязанному к readyRead(), останавливаем таймер, читаем ответ, и если это не уведомление, то переходим к следующему сообщению в очереди, иначе заново заводим таймер и продолжаем ждать ответ. Если слот, привязанный к таймеру успевает выполниться - беда, сервер не отвечает, тоже каким-либо образом эта ситуация обрабатывается. Упрощенно так. Можно еще что-то типа конечного автомата с его состояниями прикрутить.
Вот примерно такого ответа я и ожидал. Видимо, как я уже и думал, нужно использовать QEventLoop, если нет других решений.
Спасибо за подробный ответ Улыбающийся А state machine у меня уже реализован.
Записан
Nimbus
Гость
« Ответ #9 : Июнь 20, 2013, 08:22 »

Увы, оказалось всё не так-то просто. Сигнал readyRead() не вызывается у сокета никогда в случае если использовать QEventLoop::exec (или qApp->processEvents() в бесконечном цикле). Хоть данные на сервер и приходят, ответ отправляется, но readyRead не сигналит никак.
Повторюсь, делаю всё в одном потоке, дабы не извращаться с потоками и (о, ужас) с блокировками.

Upd: мды... Решилось всё экспериментальным путём со сменой ConnectionType'а, вместо Qt::AutoConnection поставил Qt::QueuedConnection.
« Последнее редактирование: Июнь 20, 2013, 08:40 от JC » Записан
Kurles
Бывалый
*****
Offline Offline

Сообщений: 480



Просмотр профиля
« Ответ #10 : Июнь 20, 2013, 10:34 »

Увы, оказалось всё не так-то просто. Сигнал readyRead() не вызывается у сокета никогда в случае если использовать QEventLoop::exec (или qApp->processEvents() в бесконечном цикле). Хоть данные на сервер и приходят, ответ отправляется, но readyRead не сигналит никак.
Повторюсь, делаю всё в одном потоке, дабы не извращаться с потоками и (о, ужас) с блокировками.
Опять же не понятно, зачем QEventLoop::exec() или qApp->processEvents() в бесконечном цикле использовать )
Записан

Код
C++ (Qt)
while(!asleep()) sheep++;
Nimbus
Гость
« Ответ #11 : Июнь 21, 2013, 16:55 »

Опять же не понятно, зачем QEventLoop::exec() или qApp->processEvents() в бесконечном цикле использовать )
QEventLoop::exec() безо всяких циклов =)
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


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