How to use QTcpSocket properly in the real world?
-
There's already an example called Fortune Client Example, but it's not good enough that even need a QTimer.
I wrote my own algorithm, however, it lost data sometimes. It's like:
@void DataSocket::sendData(const QByteArray &content)
{
if(state() == QAbstractSocket::ConnectedState){
// QByteArray buffer= qCompress(content);
QByteArray buffer= content;
QDataStream in(this);
in << quint32(buffer.size()) << buffer;
}
}//signal newData() is connected with slot readyRead()
void DataSocket::newData()
{
if(state() == QAbstractSocket::ConnectedState){
if( bytesAvailable() < (qint64)sizeof(quint32) ) return;
QDataStream in(this);
if(!dataSize)in >> dataSize;
if(bytesAvailable() < dataSize) {
return;
}
QByteArray array;
array.resize(dataSize);
in >> array;
// array = qUncompress(array);emit newData(array); dataSize = 0; if( bytesAvailable() ) newData(); }
}@
Did I miss something? or is there a real world example of using QTcpSocket?
-
Any suggestions here?
-
You need to have a look to the "different member functions and signals supplied by QTcpSocket":http://qt-project.org/doc/qt-4.8/qtcpsocket-members.html
Certainly there is the possibility of checking the socket with a timer trigger. However, that is not required. QTcpSocket, respectively one of the base classes has its own trigger. Have a look to the "readyRead() signal.":http://qt-project.org/doc/qt-4.8/qiodevice.html#readyRead -
[quote author="koahnig" date="1344073423"]You need to have a look to the "different member functions and signals supplied by QTcpSocket":http://qt-project.org/doc/qt-4.8/qtcpsocket-members.html
Certainly there is the possibility of checking the socket with a timer trigger. However, that is not required. QTcpSocket, respectively one of the base classes has its own trigger. Have a look to the "readyRead() signal.":http://qt-project.org/doc/qt-4.8/qiodevice.html#readyRead[/quote]Well, thanks. But I've already connected with that signal to receive new data.
The problem I met, is that I was lack of a good way to combine the data pieces without losing data.
The code snippet I show in the first thread is what I used to send and receive data. The second function is already connected with readyRead() signal, but I think there may something wrong in it leading to losing data.
Or, is there a "standard way" to handle this? -
There are two possibilities to go around this.
- Do not read, if there are not enough bytes available. That is most of the time only applicable with fixed record length.
- QTcpSocket inherits from QIODevice. QIODevice has methods seek and pos but they are not present on sequential devices such as QTcpSocket. So you have to write your own buffer and place all bytes received into that buffer.
-
Thanks, I'll check them.
-
Koahnig, could there be another reason? Please have a look at the code, I do not understand several things.
@
void DataSocket::newData() <-- this slot is called in an exec() eventloop.
{
if(state() == QAbstractSocket::ConnectedState){
if( bytesAvailable() < (qint64)sizeof(quint32) ) return;
QDataStream in(this);
if(!dataSize)in >> dataSize;
if(bytesAvailable() < dataSize) {
return;
}
QByteArray array;
array.resize(dataSize);
in >> array;
// array = qUncompress(array);emit newData(array); <-- this is signal evaluated in an event handler why send signal with the same name as this funtion? dataSize = 0; if( bytesAvailable() ) newData(); <-- while this is true there's no event handler being called }
}@
As long as the slots are not evaluated in the relevant exec() event handler there will no data be sent since the IODevice uses buffered I/O. I think, if you program this way you need to be shure, when each slot is evaluated and in what thread.
liuyanghejer.. I want to ask what you mean with
bq. but it’s not good enough that ...
? What is not good enough and what do you want to achieve?
-
Well, I have the definition below:
@
class DataSocket : public QTcpSocket
{
Q_OBJECT
public:
explicit DataSocket(QObject *parent = 0);
~DataSocket();signals:
void newData(QByteArray);public slots:
void sendData(const QByteArray &content);
void newData();protected:
quint32 dataSize;
};
@The newData() is a slot while the newData(QByteArray) is a signal. This is still ok due to different function signature, though not really good.
Since the DataSocket class is actually QTcpSocket, code beyond is all running in main thread.
However, QTcpSocket also manage another thread that receiving network data. Even the main thread is already run in newData() slot, new data may come at the same time. If this really happens, I doubt that the signal will be emitted.
Code:
@
if( bytesAvailable() ) newData();
@This is used for proceeding the new data.
What I want to achieve is to prevent using QTimer. Because QTimer is much more heavier than only a function call.
Let's see another socket code I copied from PokemonOnline(they use qt , https://github.com/coyotte508/pokemon-online):
@
void Network::onReceipt()
{
if (commandStarted == false) {
/* There it's a new message we are receiving.
To start receiving it we must know its length, i.e. the 2 first bytes /
if (this->bytesAvailable() < 4) {
return;
}
/ Ok now we can start /
commandStarted=true;
/ getting the length of the message /
char c1, c2, c3, c4;
this->getChar(&c1), this->getChar(&c2); this->getChar(&c3), this->getChar(&c4);
remainingLength= (uchar(c1) << 24) + (uchar(c2) << 16) + (uchar(c3) << 8) + uchar(c4);
/ Recursive call to write less code =) /
onReceipt();
} else {
/ Checking if the command is complete! /
if (this->bytesAvailable() >= remainingLength) {
emit isFull(read(remainingLength));
commandStarted = false;
/ Recursive call to spare code =), there may be still data pending */
onReceipt();
}
}
}
@Leave the protocol(their own) code along. It also calls onReceipt() again to deal with pending data. This is a common way I think.
-
[quote author="franku" date="1344373695"]Koahnig, could there be another reason? Please have a look at the code, I do not understand several things.
[/quote]
That is for sure. There is only a code fragment given. My answers assume that the basics are handled ok. However, that seems to be the case judging the responses.IMHO it is the common problem, that readyRead signals are triggered before the complete information arrived. E.g. a bulk of information is arriving, let's say 1kB, but the readyRead is triggered within the first few hundred bytes. bytesAvailable will indicate only e.g. 400 Bytes. Even so, that the buffer is filled during execution of your slot and you are checking again before leaving the slot, you may read another couple hundred Bytes. However, there is always a likelihood to that the end did not arrive yet.
[quote author="koahnig" date="1344160277"]There are two possibilities to go around this.
- Do not read, if there are not enough bytes available. That is most of the time only applicable with fixed record length.
- QTcpSocket inherits from QIODevice. QIODevice has methods seek and pos but they are not present on sequential devices such as QTcpSocket. So you have to write your own buffer and place all bytes received into that buffer. [/quote]
Those are the consequences then.
Well, fixed length may be interpret also that there is some information in the being of the block telling you the size of the block. You can enter a loop and wait until the information is complete.
The other is certainly doing your own buffering.The first implementation has one big disadvantage. The routine gets stopped when the connection breaks during waiting for more data. In general it works fine besides this unpleasant effect.
The second option is preferable.
[quote author="liuyanghejerry" date="1344403700"]@
void Network::onReceipt()
{
if (commandStarted == false) {
/* There it's a new message we are receiving.
To start receiving it we must know its length, i.e. the 2 first bytes /
if (this->bytesAvailable() < 4) {
return;
}
/ Ok now we can start /
commandStarted=true;
/ getting the length of the message /
char c1, c2, c3, c4;
this->getChar(&c1), this->getChar(&c2); this->getChar(&c3), this->getChar(&c4);
remainingLength= (uchar(c1) << 24) + (uchar(c2) << 16) + (uchar(c3) << 8) + uchar(c4);
/ Recursive call to write less code =) /
onReceipt();
} else {
/ Checking if the command is complete! /
if (this->bytesAvailable() >= remainingLength) {
emit isFull(read(remainingLength));
commandStarted = false;
/ Recursive call to spare code =), there may be still data pending */
onReceipt();
}
}
}
@
[/quote]The basic thought is ok in my opinion. However, IMHO I would not do the recursion. With the recursion it is an implementation equivalent to the first option.
One simple modification would be to replace the call (recursion to onReceipt) with a return. The next entry of onReceipt would be triggered by the next readyRead. When the remainingLength is already/still available you can check if enough bytes are now available. So, the buffering would not be done with additional memory, but handled by QTcpSocket. -
Will next readyRead be emitted even if the new data comes when onReceipt() still running?
-
[quote author="liuyanghejerry" date="1344430313"]Will next readyRead be emitted even if the new data comes when onReceipt() still running?[/quote]
My knowledge of the mechanisms is very limited. Would assume that the event will be put on the stack and processed when others are finished. But i never tried to monitor.
-
[quote author="koahnig" date="1344431899"][quote author="liuyanghejerry" date="1344430313"]Will next readyRead be emitted even if the new data comes when onReceipt() still running?[/quote]
My knowledge of the mechanisms is very limited. Would assume that the event will be put on the stack and processed when others are finished. But i never tried to monitor. [/quote]
I tried and it shows I can't remove that line, or data will loss a lot if traffic is heavy.