Когда используешь 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 или я что-то упустил?
документация из 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
экземпляр в разных потокахЯ думаю, что сценарий, упомянутый в этой теме, имеет два основных случая, которые работают по-разному, но в целом 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.
Вот несколько примеров способов получения всего файла, но с использованием некоторых других частей 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
Надеюсь, это поможет.
Проблема довольно интересная.
В моей программе использование 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
,
Если 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
не пропускается и запускается снова.
У меня была такая же проблема сразу со слотом 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;
}
}