Listening and receiving data from a socket.
-
I've started over with sockets and trying to build up something that works using the fortuneclient and fortuneserver sample code.
I have created simple class called clsListener:
#include <map> #include <QAbstractSocket> #include <QDataStream> #include <QTcpServer> #include <QTcpSocket> class clsListener; typedef std::map<quint16, clsListener*> mpListeners; class clsListener : public QObject { Q_OBJECT private: static mpListeners msmpListeners; QDataStream mdsIn; QTcpServer* mpServer; quint16 muint16Port; public: clsListener(quint16 uint16Port, QObject* pParent = nullptr); ~clsListener(); static clsListener* pCreateListener(quint16 uint16Port); public slots: void onAcceptError(QAbstractSocket::SocketError socketError); void onDataIn(); void onErrorOccurred(QAbstractSocket::SocketError socketError); void onNewConnection(); };
The implementation:
#include <utility> #include "clsDebugService.h" #include "clsListener.h" //Static initialisation mpListeners clsListener::msmpListeners; /** * @brief clsListener class constructor * @param uint16Port : Port to listen on * @param pParent : Pointer to parent */ clsListener::clsListener(quint16 uint16Port, QObject* pParent) : muint16Port(uint16Port) { if ( clsListener::msmpListeners.find(uint16Port) != clsListener::msmpListeners.end() ) { throw "Port already in use by another listener!"; } clsListener::msmpListeners.insert(std::make_pair(muint16Port, this)); //Connect server signals mpServer = new QTcpServer(pParent); QObject::connect(mpServer, &QTcpServer::acceptError ,this, &clsListener::onAcceptError); QObject::connect(mpServer, &QTcpServer::newConnection ,this, &clsListener::onNewConnection); //Start listening to port if ( !mpServer->listen(QHostAddress::Any, muint16Port) ) { qdbg() << "Unable to lisen to port " << muint16Port; } qdbg() << "Listening on port: " << muint16Port; } /** * @brief Removes listener */ clsListener::~clsListener() { if ( clsListener::msmpListeners.find(muint16Port) != clsListener::msmpListeners.end() ) { clsListener::msmpListeners.erase(muint16Port); } if ( mpServer != nullptr ) { mpServer->close(); delete mpServer; } } /** * @brief This signal is emitted when accepting a new connection results in * an error. The socketError parameter describes the type of error that * occurred. * @param socketError */ void clsListener::onAcceptError(QAbstractSocket::SocketError socketError) { Q_UNUSED(socketError); } /** * @brief Slot to receive data in from client */ void clsListener::onDataIn() { QByteArray arybytMsg; mdsIn.startTransaction(); mdsIn >> arybytMsg; if ( !mdsIn.commitTransaction() ) { return; } } /** * @brief This signal is emitted after an error occurred. The socketError * parameter describes the type of error that occurred. * @param socketError is not a registered metatype, so for queued connections, * you will have to register it with Q_DECLARE_METATYPE() and * qRegisterMetaType(). */ void clsListener::onErrorOccurred(QAbstractSocket::SocketError socketError) { //Close the connection QTcpSocket* pClient = static_cast<QTcpSocket*>(mdsIn.device()); if ( pClient != nullptr ) { pClient->close(); } qdbg() << "Socket error(" << socketError << ") https://www.google.com/search?rls=en&q=socket+error+code+" << socketError << "&ie=UTF-8&oe=UTF-8" << socketError << ".php"; } /** * @brief This signal is emitted every time a new connection is available. */ void clsListener::onNewConnection() { QTcpSocket* pClient = mpServer->nextPendingConnection(); if ( pClient->isOpen() != true ) { return; } //Set-up datastream mdsIn.setDevice(pClient); mdsIn.setVersion(QDataStream::Qt_5_15); //Connect signals and slots QObject::connect(pClient, &QAbstractSocket::disconnected ,pClient, &QObject::deleteLater); QObject::connect(pClient, &QTcpSocket::readyRead ,this, &clsListener::onDataIn); QObject::connect(pClient, &QAbstractSocket::errorOccurred ,this, &clsListener::onErrorOccurred); } /** * @brief Creates a new listener * @param uint16Port : Port to listen on * @return Pointer to the new listener */ clsListener* clsListener::pCreateListener(quint16 uint16Port) { return new clsListener(uint16Port); }
Its still very much a work in progress, I'm using a web-browser as the client and my code is listening on the local IP address and port 8124. Using Safari I type into the URL:
http://localhost:8124/?{"hello":"world"}
I can see using the debugger that a new connection is established and that the signal readyRead is emitted and my slot onDataIn is called, but I receive no data. What haven't I done?
I've single stepped into:
QDataStream &operator>>
And I can see that the ba (QByteArray&) passed in as the second parameter does get data but then its cleared as the return of 'readRawData' does not match 'blockSize'.
I've tried using a QString which uses a different version of the same operator but the result is the same.
-
-
@SPlatten said in Listening and receiving data from a socket.:
?{"hello":"world"}
What should this be? At least no QDataStream data...
-
@SPlatten
While you're waiting for someone who understands better than I....You are reading data into a
QByteArray
in a transaction. But the sender has not sent aQByteArray
(has it?). So what do you expect to happen?EDIT Mine crossed with @Christian-Ehrlicher's. Let me try to hint briefly. To use
<<
, and transactions, the client must be sendingQDataStream
objects for you to receive them. Your client is not, it's just sending a stream of bytes.That means, all you can do is read whatever bytes happen to be there when
readyRead
is raised. And that could be anything from the whole string down to just 1 byte. As it stands --- without inventing some protocol for properly sending messages --- all you can do is keep accumulating received bytes until the final}
arrives. Push bytes received into a member buffer. Exit thereadyRead
handler and allow it to be called again for further bytes. Append future bytes to the pending buffer. When it contains a}
you have received the complete client "message".If the client were a Qt program, and it was using
QDataStream
to send aQByteArray
for the message, then you could usesocket >> QByteArray
and a transaction. When bytes arrived the transaction would look to see whether the wholeQByteArray
was there. If not all bytes arrived yet, it would buffer those arrived andcommitTransaction()
returnsfalse
. When more bytes arrive, it would try appending them. When the whole array received,commitTransaction()
will returntrue
. -
@Christian-Ehrlicher , this is just test data on the URL to see that something is being received.
-
@JonB , how the data is received is nothing to do with how it is sent, I can receive the same data as either a QString or array, the content is the same. I've also tested the same code using a QString, the result is the same.
What I mean by this is that receiving data in a QByteArray or QString makes no difference, the content would be the same, the only difference is the way 0 (nulls) are handled.
-
@SPlatten
You are failing to understand. Nothing to do withQByteArray
vsQString
.how the data is received is nothing to do with how it is sent
Not at all true, given that you are using
QByteArray
, and transactions. How it is sent totally does matter. Nothing other than client sending aQByteArray
via aQDataStream
will satisfy your server receiver code. You are thinking, somehow, that bytes sent withoutQDataStream
can be received asQDataStream
, which they can't.I have typed in a detailed explanation in an EDIT to my previous to try to help you. Until you understand that you are going to struggle, I urge you to read it carefully.... :)
-
@JonB , It was my understanding which is obviously wrong that I could receive any data on the socket using a QDataStream, in my testing I'm trying to receive data from a browser which is a standard HTTP request.
I'm now replacing the QDataStream receive data with:
void clsListener::onDataIn() { QByteArray arybytMsg; while( mpClient->bytesAvailable() ) { QByteArray arybytMsg = mpClient->readAll(); qdbg() << "HERE"; } }
Just to see if that resolves the issue.
[Edit] This now works and I am receiving the data.
-
@SPlatten said in Listening and receiving data from a socket.:
It was my understanding which is obviously wrong
It's all in the docs: "A data stream is a binary stream of encoded information..."
It really helps (as already told you many times) to read the documentation of a class before using it. -
@Christian-Ehrlicher , thank you, that just takes time which I don't feel I have.
-
@SPlatten said in Listening and receiving data from a socket.:
that just takes time which I don't feel I have.
You would have solved the issue by yourself 17hours ago when you would have read the documentation.
-
@SPlatten
This is getting better, but you are still assuming that onereadyRead
call toonDataIn
allows you to write a loop onbytesAvailable()
and that will accumulate all bytes sent. It won't. It may do because your message data is so small, but not necessarily, and not if the data gets larger. (Try sending a very long [e.g. many K? megabytes?] string!)I showed you the only correct algorithm. Save the data received into a persistent (e.g. member variable) buffer. Append to that as more arrives. Check if you have now received the terminating
}
from your sending "protocol". When you do, you have the complete message. Period. -
@SPlatten said in Listening and receiving data from a socket.:
how the data is received is nothing to do with how it is sent, I can receive the same data as either a QString or array, the content is the same. I've also tested the same code using a QString, the result is the same.
That's not true, you have to read data in the same way you sent them when you are using
QDataStream
.
As TCP is a stream socket, you can not be sure all data have been received, sostartTransaction()
in combination withcommitTransaction()
will simply reception side. When not all data are received,QDataStream
will rollback the readed data and so you can retry reading on next received data chunk.But to ensure this works, you have to read what you have transfered, in the same order.
-
@SPlatten said in Listening and receiving data from a socket.:
that just takes time which I don't feel I have
But you have time to ask in a forum and wait for an answer?!