how to transfer QImage from QLocalServer to QLocalSocket
-
Here a minimal, working example
#include <QtCore> #include <QtNetwork> class Server : public QObject { Q_OBJECT public: Server(const QByteArray &dataToSend) : m_dataToSend(dataToSend) { if (!m_server.listen(QHostAddress::Any, 12345)) { qFatal(qPrintable("Can not listen to local socket 'TestConnection': " + m_server.errorString())); } connect(&m_server, &QTcpServer::newConnection, this, &Server::onNewConnection); } private Q_SLOTS: void onNewConnection() { if (auto conn = m_server.nextPendingConnection()) { qDebug() << "New connection"; connect(conn, &QLocalSocket::bytesWritten, this, [this, conn](qint64 count) { // this will only work when there is excatly one connection m_bytesSent += count; if (m_bytesSent == m_dataToSend.size()) { conn->deleteLater(); } }); QDataStream ds(conn); ds << m_dataToSend; } } private: QTcpServer m_server; QByteArray m_dataToSend; qint64 m_bytesSent = 0; }; class Client : public QObject { Q_OBJECT public: Client() { connect(&m_socket, &QLocalSocket::readyRead, this, [this]() { QDataStream ds(&m_socket); ds.startTransaction(); ds >> m_receivedData; if (ds.commitTransaction()) { qDebug() << "Received all data:" << m_receivedData.size(); QCoreApplication::quit(); } else { qDebug() << "Waiting for more data, bytesAvailable:" << m_socket.bytesAvailable(); } }); m_socket.connectToHost(QHostAddress::LocalHost, 12345); } QByteArray receivedData() const { return m_receivedData; } private: QTcpSocket m_socket; QByteArray m_receivedData; }; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QByteArray ba(1024 * 1024 * 10, 'b'); Server s(ba); Client c; int ret = app.exec(); if (c.receivedData() == ba) { qDebug() << "Recevied the correct data"; } else { qWarning() << "Received data *DOES NOT MATCH*!"; } return ret; } #include "main.moc" -
Here a minimal, working example
#include <QtCore> #include <QtNetwork> class Server : public QObject { Q_OBJECT public: Server(const QByteArray &dataToSend) : m_dataToSend(dataToSend) { if (!m_server.listen(QHostAddress::Any, 12345)) { qFatal(qPrintable("Can not listen to local socket 'TestConnection': " + m_server.errorString())); } connect(&m_server, &QTcpServer::newConnection, this, &Server::onNewConnection); } private Q_SLOTS: void onNewConnection() { if (auto conn = m_server.nextPendingConnection()) { qDebug() << "New connection"; connect(conn, &QLocalSocket::bytesWritten, this, [this, conn](qint64 count) { // this will only work when there is excatly one connection m_bytesSent += count; if (m_bytesSent == m_dataToSend.size()) { conn->deleteLater(); } }); QDataStream ds(conn); ds << m_dataToSend; } } private: QTcpServer m_server; QByteArray m_dataToSend; qint64 m_bytesSent = 0; }; class Client : public QObject { Q_OBJECT public: Client() { connect(&m_socket, &QLocalSocket::readyRead, this, [this]() { QDataStream ds(&m_socket); ds.startTransaction(); ds >> m_receivedData; if (ds.commitTransaction()) { qDebug() << "Received all data:" << m_receivedData.size(); QCoreApplication::quit(); } else { qDebug() << "Waiting for more data, bytesAvailable:" << m_socket.bytesAvailable(); } }); m_socket.connectToHost(QHostAddress::LocalHost, 12345); } QByteArray receivedData() const { return m_receivedData; } private: QTcpSocket m_socket; QByteArray m_receivedData; }; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QByteArray ba(1024 * 1024 * 10, 'b'); Server s(ba); Client c; int ret = app.exec(); if (c.receivedData() == ba) { qDebug() << "Recevied the correct data"; } else { qWarning() << "Received data *DOES NOT MATCH*!"; } return ret; } #include "main.moc"@Christian-Ehrlicher said in how to transfer QImage from QLocalServer to QLocalSocket:
QDataStream ds(&m_socket);Interesting that works, with the data stream as a local variable inside the slot lambda. I would have made its scope outside the slot, so that it lasted as long as the transaction. So the transaction cancels on slot exit, and re-examines the whole of
m_socketdata each time round. You did test this works against multiplereadRead()s for one transaction, didn't you? :) -
@Christian-Ehrlicher said in how to transfer QImage from QLocalServer to QLocalSocket:
QDataStream ds(&m_socket);Interesting that works, with the data stream as a local variable inside the slot lambda. I would have made its scope outside the slot, so that it lasted as long as the transaction. So the transaction cancels on slot exit, and re-examines the whole of
m_socketdata each time round. You did test this works against multiplereadRead()s for one transaction, didn't you? :)@JonB said in how to transfer QImage from QLocalServer to QLocalSocket:
Interesting that works, with the data stream as a local variable inside the slot lambda. I would have made its scope outside the slot, so that it lasted as long as the transaction.
I don't understand this.
See also https://doc.qt.io/qt-5/qdatastream.html#using-read-transactions"If no full packet is received, this code restores the stream to the initial position, after which you need to wait for more data to arrive."
Also the QDataStream::commitTransaction() code is useful here:
bool QDataStream::commitTransaction() { CHECK_STREAM_TRANSACTION_PRECOND(false) if (--d->transactionDepth == 0) { CHECK_STREAM_PRECOND(false) if (q_status == ReadPastEnd) { dev->rollbackTransaction(); <-- this is what you were looking for return false; } dev->commitTransaction(); } return q_status == Ok; } -
@JonB said in how to transfer QImage from QLocalServer to QLocalSocket:
Interesting that works, with the data stream as a local variable inside the slot lambda. I would have made its scope outside the slot, so that it lasted as long as the transaction.
I don't understand this.
See also https://doc.qt.io/qt-5/qdatastream.html#using-read-transactions"If no full packet is received, this code restores the stream to the initial position, after which you need to wait for more data to arrive."
Also the QDataStream::commitTransaction() code is useful here:
bool QDataStream::commitTransaction() { CHECK_STREAM_TRANSACTION_PRECOND(false) if (--d->transactionDepth == 0) { CHECK_STREAM_PRECOND(false) if (q_status == ReadPastEnd) { dev->rollbackTransaction(); <-- this is what you were looking for return false; } dev->commitTransaction(); } return q_status == Ok; }@Christian-Ehrlicher said in how to transfer QImage from QLocalServer to QLocalSocket:
I don't understand this.
That's OK, I don't understand why you choose to write it your way either :)
You have the
QDataStreamlocal to the slot. It can start a transaction, and then if it does not have all the data the slot exits and theQDataStreamis destroyed. I don't really understand where the partial data read from the socket has been stored for re-use next time round on the nextreadyRead.I also see void QDataStream::startTransaction()
For sequential devices, read data will be duplicated internally to allow recovery in case of incomplete reads.
So first there is some copying which I guess must be repeated, seems wasteful.
I understand the/your use of
startTransaction/commitTransaction(). What I do not understand is having theQDataStream ds(&m_socket)in the slot, and allowing it to be destroyed when the slot cannot read all the data to end a transaction.OK, now I understand better: your
ds >> m_receivedData;appends whatever data has been received off to a buffer.OK then, here is the bit I do not understand now. When the server sends a whole
QByteArrayto the socket viaQDataStream, theQDataStreammust put in something like a "byte count" in the header/start of the data stream, so that the client receiver will know that/know when the receiving transaction has read all data. Correct, or wrong?Then when the client receiver goes
QDataStream ds(&m_socket); ds.startTransaction()does that not read the "byte count" at the head of the data received, so it will know how many total bytes the server will be sending for theQByteArray? Correct, or wrong?If it does work that way, if you kill off the
QDataStreamat the read side because reading the wholeQByteArrayis incomplete and you allow theQDataStreamto go out of scope, when you next enter that slot and create a newQDataStream/startTransaction()how does it know how many more bytes received will end the transaction and complete the transfer of the originalQByteArrayfrom the sender? -
@Christian-Ehrlicher said in how to transfer QImage from QLocalServer to QLocalSocket:
I don't understand this.
That's OK, I don't understand why you choose to write it your way either :)
You have the
QDataStreamlocal to the slot. It can start a transaction, and then if it does not have all the data the slot exits and theQDataStreamis destroyed. I don't really understand where the partial data read from the socket has been stored for re-use next time round on the nextreadyRead.I also see void QDataStream::startTransaction()
For sequential devices, read data will be duplicated internally to allow recovery in case of incomplete reads.
So first there is some copying which I guess must be repeated, seems wasteful.
I understand the/your use of
startTransaction/commitTransaction(). What I do not understand is having theQDataStream ds(&m_socket)in the slot, and allowing it to be destroyed when the slot cannot read all the data to end a transaction.OK, now I understand better: your
ds >> m_receivedData;appends whatever data has been received off to a buffer.OK then, here is the bit I do not understand now. When the server sends a whole
QByteArrayto the socket viaQDataStream, theQDataStreammust put in something like a "byte count" in the header/start of the data stream, so that the client receiver will know that/know when the receiving transaction has read all data. Correct, or wrong?Then when the client receiver goes
QDataStream ds(&m_socket); ds.startTransaction()does that not read the "byte count" at the head of the data received, so it will know how many total bytes the server will be sending for theQByteArray? Correct, or wrong?If it does work that way, if you kill off the
QDataStreamat the read side because reading the wholeQByteArrayis incomplete and you allow theQDataStreamto go out of scope, when you next enter that slot and create a newQDataStream/startTransaction()how does it know how many more bytes received will end the transaction and complete the transfer of the originalQByteArrayfrom the sender?@JonB said in how to transfer QImage from QLocalServer to QLocalSocket:
don't really understand where the partial data read from the socket has been stored for re-use next time round on the next readyRead.
how does it know how many more bytes received will end the transaction and complete the transfer of the original QByteArray from the sender?In the QTcpSocket -> abort/rollbackTransaction() restores the state of the underlying QIODevice (if the IODevice support this which is imo not the case for QSerialPort but I might be wrong)
, the QDataStream must put in something like a "byte count" in the header/start of the data stream, so that the client receiver will know that/know when the receiving transaction has read all data. Correct, or wrong?
That's the reason why we use QDataStream here - QDataStream adds some bytes so it can be properly deserialized later on. But not much logic tbh.
Maybe the code of QIODevice::rollbackTransaction() helps you:
void QIODevice::rollbackTransaction() { Q_D(QIODevice); if (!d->transactionStarted) { checkWarnMessage(this, "rollbackTransaction", "Called while no transaction in progress"); return; } if (!d->isSequential()) <-- here is the magic for non-sequential iodevices like e.g QTcpSocket or QLocalSocket or QFile d->seekBuffer(d->transactionPos); d->transactionStarted = false; d->transactionPos = 0; } -
Ok, now you've got me - QTcpSocket is sequential... but my code works as expected (and as I've done multiple times iirc) - need to investigate.
-
Ok, the magic is in QIODevicePrivate::read()
{ Q_Q(QIODevice); const bool buffered = (openMode & QIODevice::Unbuffered) == 0; const bool sequential = isSequential(); const bool keepDataInBuffer = sequential ? peeking || transactionStarted : peeking && buffered; const qint64 savedPos = pos; qint64 readSoFar = 0; bool madeBufferReadsOnly = true; bool deviceAtEof = false; char *readPtr = data; qint64 bufferPos = (sequential && transactionStarted) ? transactionPos : Q_INT64_C(0); // Try reading from the buffer. qint64 bufferReadChunkSize = keepDataInBuffer ? buffer.peek(data, maxSize, bufferPos) <-- the data is 'peeked', not read : buffer.read(data, maxSize); if (bufferReadChunkSize > 0) { -
Ok, the magic is in QIODevicePrivate::read()
{ Q_Q(QIODevice); const bool buffered = (openMode & QIODevice::Unbuffered) == 0; const bool sequential = isSequential(); const bool keepDataInBuffer = sequential ? peeking || transactionStarted : peeking && buffered; const qint64 savedPos = pos; qint64 readSoFar = 0; bool madeBufferReadsOnly = true; bool deviceAtEof = false; char *readPtr = data; qint64 bufferPos = (sequential && transactionStarted) ? transactionPos : Q_INT64_C(0); // Try reading from the buffer. qint64 bufferReadChunkSize = keepDataInBuffer ? buffer.peek(data, maxSize, bufferPos) <-- the data is 'peeked', not read : buffer.read(data, maxSize); if (bufferReadChunkSize > 0) {@Christian-Ehrlicher
Now you're talking about buffering to revert? The sender is sending an arbitrary sizedQByteArray. The OP has an image, could be big. Let's say it's a 1GB byte array (a big image, a movie, ...). So the receiver client is going to need to buffer > 1GB of data? Is whatever buffer is being used big enough/limited? -
@Christian-Ehrlicher
Now you're talking about buffering to revert? The sender is sending an arbitrary sizedQByteArray. The OP has an image, could be big. Let's say it's a 1GB byte array (a big image, a movie, ...). So the receiver client is going to need to buffer > 1GB of data? Is whatever buffer is being used big enough/limited?@JonB said in how to transfer QImage from QLocalServer to QLocalSocket:
Is whatever buffer is being used big enough/limited?
2GB since it's a QByteArray with Qt5, 2^63Bytes with Qt6.
But even when you don't destroy the QDataStream it would be buffered in QDataStream - so no difference.
Sending more than 1GB with QDataStream + transaction is ... not useful. Using a simple own protocol is much better for such a task - also on the sender side since you then don't have to load the 1GB directly into the memory. -
@JonB said in how to transfer QImage from QLocalServer to QLocalSocket:
Is whatever buffer is being used big enough/limited?
2GB since it's a QByteArray with Qt5, 2^63Bytes with Qt6.
But even when you don't destroy the QDataStream it would be buffered in QDataStream - so no difference.
Sending more than 1GB with QDataStream + transaction is ... not useful. Using a simple own protocol is much better for such a task - also on the sender side since you then don't have to load the 1GB directly into the memory.@Christian-Ehrlicher
Exactly :)But even when you don't destroy the QDataStream it would be buffered in QDataStream - so no difference.
Yes, true.
I kind of get that your code works, just intuitively I would have expected to keep the
QDataStreamin scope across thestart/commitTransaction()calls, not local to the slot, so they operate on the same instance. -
@Christian-Ehrlicher @JonB Thanks for your reply.
Finally I got the solutions I used QDataStream.I was doing mistake at client side code was giving wrong format and size of the image
BEFORE
QImage image((uchar *)imageData.data(),252,252,QImage::Format_ARGB32);AFTER
QImage image((uchar *)imageData.data(),640,480,QImage::Format_RGB888);