Чтение из QTcpSocket с помощью QDataStream
мне нужно отправить двоичные данные через QTcpSocket
. Я думал об использовании QDataStream
, но я столкнулся с проблемой-он молча терпит неудачу, если данные не поступили в то время, когда я пытаюсь читать.
например, если у меня есть этот код:
QString str;
stream >> str;
он будет терпеть неудачу молча, если в настоящее время в сокете нет данных. Есть ли способ сказать ему блокировать вместо этого?
3 ответов
проблема немного серьезнее. Сокет может получать данные кусками, поэтому даже если вы будете ждать waitForReadyRead
он может потерпеть неудачу, так как недостаточно данных, чтобы сразу прочитать какой-то объект.
Чтобы решить эту проблему, вы должны сначала отправить размер данных, а затем фактические данные.
Отправить код:
QByteArray block;
QDataStream sendStream(&block, QIODevice::ReadWrite);
sendStream << quint16(0) << str;
sendStream.device()->seek(0);
sendStream << (quint16)(block.size() - sizeof(quint16));
tcpSocket->write(block);
на приемнике вы должны ждать пока размер доступных данных соотвествует. Код приемника выглядит примерно так:
void SomeClass::slotReadClient() { // slot connected to readyRead signal of QTcpSocket
QTcpSocket *tcpSocket = (QTcpSocket*)sender();
QDataStream clientReadStream(tcpSocket);
while(true) {
if (!next_block_size) {
if (tcpSocket->bytesAvailable() < sizeof(quint16)) { // are size data available
break;
}
clientReadStream >> next_block_size;
}
if (tcpSocket->bytesAvailable() < next_block_size) {
break;
}
QString str;
clientReadStream >> str;
next_block_size = 0;
}
}
маленький обновление, основанное на документация можно читать QString без добавления дополнительной информации о размере, поскольку QString, переданная QDataStream, содержит информацию о размере. Размер можно проверить так:
void SomeClass::slotReadClient() { // slot connected to readyRead signal of QTcpSocket
QTcpSocket *tcpSocket = (QTcpSocket*)sender();
while(true) {
if (tcpSocket->bytesAvailable() < 4) {
break;
}
char buffer[4]
quint32 peekedSize;
tcpSocket->peek(buffer, 4);
peekedSize = qFromBigEndian<quint32>(buffer); // default endian in QDataStream
if (peekedSize==0xffffffffu) // null string
peekedSize = 0;
peekedSize += 4;
if (tcpSocket->bytesAvailable() < peekedSize) {
break;
}
// here all required for QString data are available
QString str;
QDataStream(tcpSocket) >> str;
emit stringHasBeenRead(str);
}
}
я переработал код из идеи @Marek и создал 2 класса-BlockReader и BlockWriter:
пример использования:
// Write block to the socket.
BlockWriter(socket).stream() << QDir("C:/Windows").entryList() << QString("Hello World!");
....
// Now read the block from the socket.
QStringList infoList;
QString s;
BlockReader(socket).stream() >> infoList >> s;
qDebug() << infoList << s;
BlockReader:
class BlockReader
{
public:
BlockReader(QIODevice *io)
{
buffer.open(QIODevice::ReadWrite);
_stream.setVersion(QDataStream::Qt_4_8);
_stream.setDevice(&buffer);
quint64 blockSize;
// Read the size.
readMax(io, sizeof(blockSize));
buffer.seek(0);
_stream >> blockSize;
// Read the rest of the data.
readMax(io, blockSize);
buffer.seek(sizeof(blockSize));
}
QDataStream& stream()
{
return _stream;
}
private:
// Blocking reads data from socket until buffer size becomes exactly n. No
// additional data is read from the socket.
void readMax(QIODevice *io, int n)
{
while (buffer.size() < n) {
if (!io->bytesAvailable()) {
io->waitForReadyRead(30000);
}
buffer.write(io->read(n - buffer.size()));
}
}
QBuffer buffer;
QDataStream _stream;
};
BlockWriter:
class BlockWriter
{
public:
BlockWriter(QIODevice *io)
{
buffer.open(QIODevice::WriteOnly);
this->io = io;
_stream.setVersion(QDataStream::Qt_4_8);
_stream.setDevice(&buffer);
// Placeholder for the size. We will get the value
// at the end.
_stream << quint64(0);
}
~BlockWriter()
{
// Write the real size.
_stream.device()->seek(0);
_stream << (quint64) buffer.size();
// Flush to the device.
io->write(buffer.buffer());
}
QDataStream &stream()
{
return _stream;
}
private:
QBuffer buffer;
QDataStream _stream;
QIODevice *io;
};
вы можете вызвать функцию qtcpsocket:: waitForReadyRead, которая будет блокировать, пока данные не будут доступны, или подключиться к сигналу readyRead() и когда ваш слот вызывается, а затем читать из потока.