Как сделать так, чтобы сигналы readyRead () от QTcpSocket не могли быть пропущены?

Когда используешь QTcpSocket чтобы получить данные, сигнал для использования readyRead(), который сигнализирует, что новые данные доступны.
Однако, когда вы находитесь в соответствующей реализации слота для чтения данных, никаких дополнительных readyRead() будет выпущен
Это может иметь смысл, поскольку вы уже находитесь в функции, где вы читаете все доступные данные.

Описание проблемы

Однако предположим следующую реализацию этого слота:

void readSocketData()
{
datacounter += socket->readAll().length();
qDebug() << datacounter;
}

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

Один из способов минимизировать проблему (но не полностью ее избежать)

Конечно, мы можем изменить слот так:

void readSocketData()
{
while(socket->bytesAvailable())
datacounter += socket->readAll().length();
qDebug() << datacounter;
}

Тем не менее, мы не решили проблему. Все еще возможно, что данные поступают сразу после socket->bytesAvailable()-check (и даже размещение / другой проверки в абсолютном конце функции не решает эту проблему).

Убедитесь, что сможете воспроизвести проблему

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

void readSocketData()
{
datacounter += socket->readAll().length();
qDebug() << datacounter;

// wait, to make sure that some data arrived
QEventLoop loop;
QTimer::singleShot(1000, &loop, SLOT(quit()));
loop.exec();
}

Затем я позволил другому приложению отправить 100 000 байтов данных.
Вот что происходит:

новое соединение!
32768 (или 16К или 48К)

Первая часть сообщения читается, но конец больше не читается, так как readyRead() больше не будет звонить

Мой вопрос: как лучше всего быть уверенным, что эта проблема никогда не возникает?

Возможное решение

Одно из решений, которое я нашел, — снова вызвать тот же самый слот в конце и проверить в начале слота, есть ли еще какие-либо данные для чтения:

void readSocketData(bool selfCall) // default parameter selfCall=false in .h
{
if (selfCall && !socket->bytesAvailable())
return;

datacounter += socket->readAll().length();
qDebug() << datacounter;

QEventLoop loop;
QTimer::singleShot(1000, &loop, SLOT(quit()));
loop.exec();

QTimer::singleShot(0, this, SLOT(readSocketDataSelfCall()));
}

void readSocketDataSelfCall()
{
readSocketData(true);
}

Поскольку я не называю слот напрямую, а использую QTimer::singleShot()Я предполагаю, что QTcpSocket не могу знать, что я снова вызываю слот, так что проблема в том, readyRead() больше не может произойти

Причина, по которой я включил параметр bool selfCall является то, что слот, который вызывается QTcpSocket не может завершиться раньше, иначе та же проблема может возникнуть снова, когда данные поступают точно в неподходящий момент и readyRead() не излучается

Это действительно лучшее решение для решения моей проблемы?
Является ли существование этой проблемы ошибкой проектирования в Qt или я что-то упустил?

26

Решение

документация из QIODevice::readyRead состояния:

Если вы повторно входите в цикл обработки событий или вызываете waitForReadyRead () внутри слота
подключенный к сигналу readyRead (), сигнал не будет переиздан.

Итак, убедитесь, что вы не вызов waitForReadyRead и что ты не сделайте любую обработку событий в вашем слоте, и проблема должна исчезнуть. (См. Ниже для получения дополнительной информации.)


readyRead сигнал испускается QAbstractSocketPrivate::emitReadyRead следующее:

if (!emittedReadyRead && channel == currentReadChannel) {
QScopedValueRollback<bool> r(emittedReadyRead);
emittedReadyRead = true;
emit q->readyRead();
}

emittedReadyRead переменная откатывается до false как только if блок выходит из области видимости (сделано QScopedValueRollback). Так что единственный шанс пропустить readyRead сигнал, когда поток управления достигает if состояние снова до обработка последнего readyRead сигнал закончен.

Чтобы этого не случилось, убедитесь, что вы

  • не вызов QIODevice::waitForReadyRead внутри вашего слота,
  • не выполнять обработку событий внутри вашего слота (например, создание экземпляра QEventLoop или звонит QApplication::processEvents),
  • не использовать то же самое QTcpSocket экземпляр в разных потоках
8

Другие решения

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

Первый случай: однопоточное применение.

Qt использует системный вызов select () для опроса дескриптора открытого файла на предмет любых изменений или доступных операций. Простое высказывание в каждом цикле Qt проверяет, есть ли у любого из открытых файловых дескрипторов данные, доступные для чтения / закрытия и т. Д. Таким образом, в однопоточном потоке приложения выглядит так (часть кода упрощена)

int mainLoop(...) {
select(...);
foreach( descriptor which has new data available ) {
find appropriate handler
emit readyRead;
}
}

void slotReadyRead() {
some code;
}

Так что же произойдет, если новые данные поступят, пока программа все еще находится в slotReadyRead … честно говоря, ничего особенного. ОС будет буферизировать данные, и как только управление вернется к следующему выполнению select (), ОС уведомит программное обеспечение о наличии данных, доступных для определенного дескриптора файла. Он работает абсолютно одинаково для сокетов / файлов TCP и т. Д.

Я могу представить себе ситуации, когда (в случае действительно длительных задержек в slotReadyRead и при поступлении большого количества данных) вы можете столкнуться с переполнением буферов OS FIFO (например, для последовательных портов), но это скорее связано с плохим дизайном программного обеспечения, чем с Проблемы с QT или OS.

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

Второй сценарий: многопоточное приложение

На самом деле этот сценарий не так уж сильно отличается от 1) ожидать, что вы должны правильно спроектировать то, что происходит в каждом из ваших потоков. Если вы сохраните основной цикл с легковесными «обработчиками псевдо-прерываний», вы будете в порядке и сохраните логику обработки в других потоках, но эта логика должна работать с вашими собственными буферами предварительной выборки, а не с QIODevice.

6

Вот несколько примеров способов получения всего файла, но с использованием некоторых других частей API QNetwork:

http://qt-project.org/doc/qt-4.8/network-downloadmanager.html

http://qt-project.org/doc/qt-4.8/network-download.html

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

Если вы все еще хотите использовать API нижнего уровня, вот пост с отличным способом обработки буферов:

Внутри вашего readSocketData() сделать что-то вроде этого:

if (bytesAvailable() < 256)
return;
QByteArray data = read(256);

http://www.qtcentre.org/threads/11494-QTcpSocket-readyRead-and-buffer-size

РЕДАКТИРОВАТЬ: Дополнительные примеры того, как взаимодействовать с QTCPSockets:

http://qt-project.org/doc/qt-4.8/network-fortuneserver.html

http://qt-project.org/doc/qt-4.8/network-fortuneclient.html

http://qt-project.org/doc/qt-4.8/network-blockingfortuneclient.html

Надеюсь, это поможет.

0

Проблема довольно интересная.

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

О вашем решении. Я не думаю, что это лучше, чем это:

void readSocketData()
{
while(socket->bytesAvailable())
{
datacounter += socket->readAll().length();
qDebug() << datacounter;

QEventLoop loop;
QTimer::singleShot(1000, &loop, SLOT(quit()));
loop.exec();
}
}

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

Также вы можете связаться с Qt::QueuedConnection,

0

Если QProgressDialog должен отображаться при получении данных из сокета, он работает только при наличии QApplication::processEvents() отправлены (например, QProgessDialog::setValue(int) Methode). Это, конечно, приводит к потере readyRead сигналы, как указано выше.

Таким образом, мой обходной путь — цикл while, включающий команду processEvents, такую ​​как:

void slot_readSocketData() {
while (m_pSocket->bytesAvailable()) {
m_sReceived.append(m_pSocket->readAll());
m_pProgessDialog->setValue(++m_iCnt);
}//while
}//slot_readSocketData

Если слот вызывается один раз, любой дополнительный readyRead сигналы могут быть проигнорированы, потому что bytesAvailable() всегда возвращает фактическое число после processEvents вызов. Только при приостановке потока цикл while заканчивается. Но тогда следующий readReady не пропускается и запускается снова.

0

У меня была такая же проблема сразу со слотом readyRead. Я не согласен с принятым ответом; это не решает проблему. Использование bytesAvailable, как описано Amartel, было единственным надежным решением, которое я нашел. Qt :: QueuedConnection не имеет никакого эффекта. В следующем примере я десериализирую пользовательский тип, поэтому легко предсказать минимальный размер байта. Он никогда не пропускает данные.

void MyFunExample::readyRead()
{
bool done = false;

while (!done)
{

in_.startTransaction();

DataLinkListStruct st;

in_ >> st;

if (!in_.commitTransaction())
qDebug() << "Failed to commit transaction.";

switch (st.type)
{
case  DataLinkXmitType::Matrix:

for ( int i=0;i<st.numLists;++i)
{
for ( auto it=st.data[i].begin();it!=st.data[i].end();++it )
{
qDebug() << (*it).toString();
}
}
break;

case DataLinkXmitType::SingleValue:

qDebug() << st.value.toString();
break;

case DataLinkXmitType::Map:

for (auto it=st.mapData.begin();it!=st.mapData.end();++it)
{
qDebug() << it.key() << " == " << it.value().toString();
}
break;
}

if ( client_->QIODevice::bytesAvailable() < sizeof(DataLinkListStruct) )
done = true;
}
}
0
По вопросам рекламы [email protected]