Почему мой код не может прочитать количество байтов, доступных в QTcpSocket? Идеи?

У меня есть следующий фрагмент кода в теле цикла, отвечающего за чтение данных из QTcpSocket (nntp — это указатель на QTcpSocket).

std::vector<char> buffer;
int bytesAvailable = nntp->bytesAvailable();
qDebug() << "bytesAvailable: "<<bytesAvailable;
if(bytesAvailable <= 0) break;
buffer.resize(bytesAvailable);
bytesRead = nntp->read(&buffer[0], bytesAvailable);
qDebug() << (nntp->state() == QAbstractSocket::ConnectedState);
qDebug() << "bytesRead: "<<bytesRead;

Периодически выдает что-то вроде следующего:

bytesAvailable:  24 
true 
bytesRead:  0 

И мой код оттуда ведет себя неправильно. Мне это кажется очень странным и говорит о том, что я совершенно неправильно понимаю, как работают QTcpSockets. Конечно, если bytesAvailable > 0, то последующее чтение будет означать, что байты bytesAvailable могут быть прочитаны в буфер, и в этом случае bytesRead должен == bytesAvailable. Или я что-то упускаю? В настоящее время я подозреваю, что это может быть какое-то повреждение памяти.

РЕДАКТИРОВАТЬ: добавление некоторых сообщений nntp.errorString() сообщает, что во время этого сбоя «время ожидания сетевой операции истекло». Мне нужно выяснить, что это значит... (идеи?)

РЕДАКТИРОВАТЬ 2: Кажется, что «время работы сети истекло» просто означает, что время чтения истекло. Когда код работает как надо (то есть с перерывами), я все еще получаю эту «ошибку».

РЕДАКТИРОВАТЬ 3: Полный алгоритмический контекст для приведенного выше фрагмента кода можно найти по этой ссылке pastebin.

РЕДАКТИРОВАТЬ 4: немного другая версия функции в РЕДАКТИРОВАТЬ 3, но все же с теми же проблемами, тем не менее, находится в этой новой ссылке pastebin


person Ben J    schedule 20.05.2013    source источник
comment
Мне очень не нравится эта конструкция: &buffer[0]. Вместо этого попробуйте buffer = nntp->readAll();.   -  person Amartel    schedule 20.05.2013
comment
Спасибо за ваше предложение, Amartel, но переключение на QByteArray, а затем выполнение readAll, а затем использование размера QByteArray для определения количества прочитанных байтов по-прежнему приводит к той же проблеме.   -  person Ben J    schedule 20.05.2013


Ответы (3)


Я обнаружил проблему: QTcpSocket, который я пытался прочитать, принадлежал другому потоку. Несмотря на то, что байты были доступны в соответствии с буфером QIODevice, из-за этого они не могли быть прочитаны.

Следовательно:

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

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

OR

Будьте сверхосторожны и

(a) использовать собственный QThread, перемещая объект, содержащий QTcpSocket, в этот QThread

тогда,

(b) использовать waitForReadyRead QTcpSocket в блокирующем цикле чтения этого другого потока.

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

В основном, используйте первый метод сигналов и слотов, если можете.

person Ben J    schedule 24.05.2013

Что, вероятно, произойдет, если этот bytesAvailable() сообщит размер данных, ожидающих во внутреннем буфере QIODevice, плюс размер, сообщенный ОС (например, в Linux, который будет получен ioctl(fd,FIONREAD,&bytesCount)).

Это само по себе имеет смысл, но чтобы иметь возможность читать эти байты из QTcpSocket без цикла событий, waitForReadyRead() должен вызываться в вашем собственном цикле. В противном случае данные из буфера ядра не попадут в буфер QIODevice.

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

Также не забудьте вызвать waitForBytesWritten(), если вы также пишете в сокет без цикла событий.

person Daniel Vérité    schedule 20.05.2013
comment
Спасибо, Даниил, это логично. На самом деле я использую waitForReadyRead в контексте полного цикла. См. РЕДАКТИРОВАТЬ 3 в исходном сообщении для ссылки. - person Ben J; 21.05.2013
comment
Я бы переместил его из внешнего цикла непосредственно перед чтением bytesAvailable(), чтобы увидеть, имеет ли это значение, тем более что внешний цикл — это do..while, а не while..do. - person Daniel Vérité; 21.05.2013
comment
Спасибо, Даниэль, я попробовал ваше предложение (см. код по ссылке EDIT 4), но у меня все еще та же проблема. - person Ben J; 21.05.2013
comment
Так много для моей теории. Что ж, если waitForReadyRead() возвращает true, а затем read() читает 0 байт, то я не понимаю, как это может произойти, если только что-то еще не прочитает содержимое тем временем. - person Daniel Vérité; 21.05.2013
comment
Это действительно сбивает меня с толку, если честно. Ничто другое не может читать содержимое. Может быть, то, как я использую потоки, портит вещи. - person Ben J; 22.05.2013

Я не сталкивался с этой точной проблемой (возможно, потому, что я использую другую версию Qt), но я бы рекомендовал попробовать переключиться с цикла на подход, управляемый событиями. Если ваш цикл выполняется в основном потоке, то объекты не могут доставлять поставленные в очередь сигналы (как это может делать класс QTcpSocket внутри), поэтому вы видите «Время ожидания сетевой операции истекло»?

Итак, подключите некоторые из основных сигналов QTcpSocket:

connect(nntp, SIGNAL(disconnected()),
        this, SLOT(onDisconnected()));
connect(nntp, SIGNAL(readyRead()),
        this, SLOT(onReadyRead()));
connect(nntp, SIGNAL(error(QAbstractSocket::SocketError)),
        this, SLOT(onSocketError(QAbstractSocket::SocketError)));

А затем поместите свой существующий код «чтения» в onReadyRead.

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

person Taylor Brandstetter    schedule 20.05.2013
comment
Спасибо, Тейлор. Я также предпочитаю использовать сигналы и слоты и, возможно, в будущем перейду на этот подход. Причина, по которой я выбрал цикл, заключается в том, что мне нужна возможность чтения заданного количества форматированных данных, которые размечены в соответствии с несколькими escape-последовательностями; внутри цикла относительно легко разобрать данные по этим токенам, причем именно в тот момент, когда я знаю, что они должны быть доступны. Сказав это, мой подход явно глючит, поэтому мне может понадобиться переключиться на СИГНАЛЫ и СЛОТЫ, как вы рекомендуете. - person Ben J; 20.05.2013