July 31, 2012

liuyanghejerry liuyanghejer..
Ant Farmer
41 posts

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:

  1. void DataSocket::sendData(const QByteArray &content)
  2. {
  3.     if(state() == QAbstractSocket::ConnectedState){
  4. //        QByteArray buffer= qCompress(content);
  5.         QByteArray buffer= content;
  6.         QDataStream in(this);
  7.         in << quint32(buffer.size()) << buffer;
  8.     }
  9. }
  10.  
  11. //signal newData() is connected with slot readyRead()
  12. void DataSocket::newData()
  13. {
  14.     if(state() == QAbstractSocket::ConnectedState){
  15.         if( bytesAvailable() < (qint64)sizeof(quint32) ) return;
  16.         QDataStream in(this);
  17.         if(!dataSize)in >> dataSize;
  18.         if(bytesAvailable() < dataSize) {
  19.             return;
  20.         }
  21.         QByteArray array;
  22.         array.resize(dataSize);
  23.         in >> array;
  24. //        array = qUncompress(array);
  25.  
  26.         emit newData(array);
  27.         dataSize = 0;
  28.         if( bytesAvailable() ) newData();
  29.     }
  30. }

Did I miss something? or is there a real world example of using QTcpSocket?

11 replies

August 4, 2012

liuyanghejerry liuyanghejer..
Ant Farmer
41 posts

Any suggestions here?

August 4, 2012

koahnig koahnig
Mad Scientist
2199 posts

You need to have a look to the different member functions and signals supplied by QTcpSocket [qt-project.org]
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. [qt-project.org]

August 5, 2012

liuyanghejerry liuyanghejer..
Ant Farmer
41 posts
koahnig wrote:
You need to have a look to the different member functions and signals supplied by QTcpSocket [qt-project.org] 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. [qt-project.org]

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?

August 5, 2012

koahnig koahnig
Mad Scientist
2199 posts

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.

August 6, 2012

liuyanghejerry liuyanghejer..
Ant Farmer
41 posts

Thanks, I’ll check them.

August 7, 2012

franku franku
Hobby Entomologist
132 posts

Koahnig, could there be another reason? Please have a look at the code, I do not understand several things.

  1. void DataSocket::newData() <-- this slot is called in an _exec()_ eventloop.
  2. {
  3.     if(state() == QAbstractSocket::ConnectedState){
  4.         if( bytesAvailable() < (qint64)sizeof(quint32) ) return;
  5.         QDataStream in(this);
  6.         if(!dataSize)in >> dataSize;
  7.         if(bytesAvailable() < dataSize) {
  8.             return;
  9.         }
  10.         QByteArray array;
  11.         array.resize(dataSize);
  12.         in >> array;
  13. //        array = qUncompress(array);
  14.  
  15.         emit newData(array); <-- this is signal evaluated in an event handler why send signal with the same name as this funtion?
  16.         dataSize = 0;
  17.         if( bytesAvailable() ) newData(); <-- while this is true there's no event handler being called
  18.      }
  19. }

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

but it’s not good enough that …

? What is not good enough and what do you want to achieve?

 Signature 

Keep in mind: This, Jen, is the internet.
.. frank

August 8, 2012

liuyanghejerry liuyanghejer..
Ant Farmer
41 posts

Well, I have the definition below:

  1. class DataSocket : public QTcpSocket
  2. {
  3.     Q_OBJECT
  4. public:
  5.     explicit DataSocket(QObject *parent = 0);
  6.     ~DataSocket();
  7.    
  8. signals:
  9.     void newData(QByteArray);
  10.  
  11. public slots:
  12.     void sendData(const QByteArray &content);
  13.     void newData();
  14.  
  15. protected:
  16.     quint32 dataSize;
  17. };

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:

  1. 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):

  1. void Network::onReceipt()
  2. {
  3.     if (commandStarted == false) {
  4.         /* There it's a new message we are receiving.
  5.            To start receiving it we must know its length, i.e. the 2 first bytes */
  6.         if (this->bytesAvailable() < 4) {
  7.             return;
  8.         }
  9.         /* Ok now we can start */
  10.         commandStarted=true;
  11.         /* getting the length of the message */
  12.         char c1, c2, c3, c4;
  13.         this->getChar(&c1), this->getChar(&c2); this->getChar(&c3), this->getChar(&c4);
  14.         remainingLength= (uchar(c1) << 24) + (uchar(c2) << 16) + (uchar(c3) << 8) + uchar(c4);
  15.         /* Recursive call to write less code =) */
  16.         onReceipt();
  17.     } else {
  18.         /* Checking if the command is complete! */
  19.         if (this->bytesAvailable() >= remainingLength) {
  20.             emit isFull(read(remainingLength));
  21.             commandStarted = false;
  22.             /* Recursive call to spare code =), there may be still data pending */
  23.             onReceipt();
  24.         }
  25.     }
  26. }

Leave the protocol(their own) code along. It also calls onReceipt() again to deal with pending data. This is a common way I think.

August 8, 2012

koahnig koahnig
Mad Scientist
2199 posts

franku wrote:
Koahnig, could there be another reason? Please have a look at the code, I do not understand several things.

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.

koahnig wrote:
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.

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.

liuyanghejerry wrote:
  1. void Network::onReceipt()
  2. {
  3.     if (commandStarted == false) {
  4.         /* There it's a new message we are receiving.
  5.            To start receiving it we must know its length, i.e. the 2 first bytes */
  6.         if (this->bytesAvailable() < 4) {
  7.             return;
  8.         }
  9.         /* Ok now we can start */
  10.         commandStarted=true;
  11.         /* getting the length of the message */
  12.         char c1, c2, c3, c4;
  13.         this->getChar(&c1), this->getChar(&c2); this->getChar(&c3), this->getChar(&c4);
  14.         remainingLength= (uchar(c1) << 24) + (uchar(c2) << 16) + (uchar(c3) << 8) + uchar(c4);
  15.         /* Recursive call to write less code =) */
  16.         onReceipt();
  17.     } else {
  18.         /* Checking if the command is complete! */
  19.         if (this->bytesAvailable() >= remainingLength) {
  20.             emit isFull(read(remainingLength));
  21.             commandStarted = false;
  22.             /* Recursive call to spare code =), there may be still data pending */
  23.             onReceipt();
  24.         }
  25.     }
  26. }

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.

August 8, 2012

liuyanghejerry liuyanghejer..
Ant Farmer
41 posts

Will next readyRead be emitted even if the new data comes when onReceipt() still running?

August 8, 2012

koahnig koahnig
Mad Scientist
2199 posts
liuyanghejerry wrote:
Will next readyRead be emitted even if the new data comes when onReceipt() still running?

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.

August 8, 2012

liuyanghejerry liuyanghejer..
Ant Farmer
41 posts
koahnig wrote:
liuyanghejerry wrote:
Will next readyRead be emitted even if the new data comes when onReceipt() still running?

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.

I tried and it shows I can’t remove that line, or data will loss a lot if traffic is heavy.

 
  ‹‹ socket recvied and signal and slot      Over which Header Section SIGNAL(customContextMenuRequested(qpoint&)) was generated ››

You must log in to post a reply. Not a member yet? Register here!