how to transfer QImage from QLocalServer to QLocalSocket
-
I have two mac apps that communicate with each other using QLocalSocket. Able to send the received QString but not able to send the received QImage Below is my code.
SERVER SIDE CODE
QImage image(":/asset/logo_active.png"); QByteArray ba; qDebug() << image.sizeInBytes() <<image.size(); ba.append((char *)image.bits(),image.sizeInBytes()); qDebug() <<ba.size(); this->mSocket->write(ba); if(!this->mSocket->waitForBytesWritten(-1)) { qDebug() << "writen Bytes error " << this->mSocket->errorString(); } this->mSocket->flush();
CLIENT SIDE CODE
QByteArray ba; ba = mLocalSocket->readAll(); QImage image((uchar *)ba.data(),1024,768,QImage::Format_RGB32); ui->labelStream->setPixmap(QPixmap::fromImage(img));
at sender 262144 is the byte-array size but at the receiver, byte-array size is 0
Do let me know if I am missing anything.
Thanks In Advance
-
@ebrahimcoder said in how to transfer QImage from QLocalServer to QLocalSocket:
Do let me know if I am missing anything.
You're missing that readyRead() is called as soon as there are bytes available, not when all is received because 'all' is not defined - it's a stream.
Use e.g. QDataStream or a custom protocol and send the byte count first. -
Hey @Christian-Ehrlicher Thanks for your quick reply.
Can you please help me with some examples.
-
The fortuneserver example shows you how to use QDataStream on a socket
-
@Christian-Ehrlicher thanks for your answer but still that example doesn't helped me. Can please share anything else.
-
@ebrahimcoder said in how to transfer QImage from QLocalServer to QLocalSocket:
Can please share anything else.
What did you try o far? Please show your current code where you're using QDataStream of the socket.
-
ServerSide code:
QDataStream T(mSocket); T.setVersion(QDataStream::Qt_5_7); QByteArray ba; ba.append((char *)image.bits(),image.sizeInBytes()); T << ba; mSocket->flush();
Client Side Code:
QByteArray imageData; QDataStream socketStream(mLocalSocket); socketStream.setVersion(QDataStream::Qt_5_7); for (;;) { socketStream.startTransaction(); socketStream >> imageData; if (socketStream.commitTransaction()) { qDebug() << imageData.size(); QImage image((uchar *)imageData.data(),252,252,QImage::Format_ARGB32); QImage imagenew = QImage::fromData(imageData); this->onImageReceived(imagenew); }else { break; } }
-
But you still did not honor the fact I told you in my first post - readyRead() is called as soon as there are bytes available, so you should not expect that after the first signal QDataStream::commitTransaction() returns true.
Save the bytes read in a QByteArray to collect the data and work on this with QDataStream. -
@ebrahimcoder
I trust this really is your code, copied & pasted? At your client side:QImage imagenew = QImage::fromData(ba);
There is no
ba
variable shown, and the code reads intoimageData
, so....[This is in addition to what @Christian-Ehrlicher has just posted above.]
-
@Christian-Ehrlicher Ya the client code is under.
connect(mLocalSocket,&QLocalSocket::readyRead, [&]() {});
Using readyRead signal
-
@JonB said in how to transfer QImage from QLocalServer to QLocalSocket:
There is no
ba
variable shown, and the code reads intoimageData
, so....@ebrahimcoder
Any comment, or do you just think this is irrelevant? Or did you change your code without saying anything about it?QDataStream socketStream(mLocalSocket);
If as shown this is a local variable in your lambda body (
{}
) code forreadyRead
signal, it will go out of scope and be destroyed on each signal call (if repeated signals). How do you think it will maintain state and satisfy transaction in that case? Similarly forQByteArray imageData;
being local which you read the bytes into. -
@ebrahimcoder
OK, so not a copy & paste of your actual code.....If your code is as you show it with local variables and repeated calling it won't work as I commented. Up to you if you want to ignore that.
Otherwise it looks like what you post is not your actual code, so all bets are off and I'll leave you to it, perhaps others can guess.
-
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_socket
data 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; }
-
@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
QDataStream
local to the slot. It can start a transaction, and then if it does not have all the data the slot exits and theQDataStream
is 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
QByteArray
to the socket viaQDataStream
, theQDataStream
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?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
QDataStream
at the read side because reading the wholeQByteArray
is incomplete and you allow theQDataStream
to 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 originalQByteArray
from 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) {