Results 1 to 3 of 3

Thread: QSslSocket/QTcpSocket not flushing data by itself

  1. #1
    Join Date
    May 2014
    Posts
    1
    Qt products
    Qt4
    Platforms
    Unix/X11 Windows

    Default QSslSocket/QTcpSocket not flushing data by itself

    Hi,

    I am writing a Client and Server application (with my own protocol) to transfer huge amounts of data (like 10GB+). I need to transfer lots of files which may be small and big.
    I am using Qt 4.8.5 as I have to support CentOS and RedHat.

    Flushing Issues :
    Everything seems to be working except that the data written to the socket is not flushing when written to the QSslSocket. So it sits in the socket buffers. I wished it flushed on its own.

    I order to flush, I need to explicitly call flush() to flush the data. (I dont want to call flush explicitly though)
    Calling the flush() function often, causes the memory usage to keep on increasing although the bytestowrite and "encrypted bytes to write" are below 20MB (as per my flushing loop which is commented) e.g. if the memory usage is 50 MB with 20 MB in bytestowrite and 20 MB in "encrypted bytes to write", when the flushing loop completes (to read more data) and there is no data in the bytestowrite nor in "encrypted bytes to write", the memory should have dropped to close to 10 MB (or it could reserve the memory for next usage). But after transferring something like 100MB its total memory usage might be 70 MB with nothing in the buffers. After 500 MB transfer, it goes close to 150MB of memory usage. And as soon as the socket is closed or left to read for more data, the memory drops to 10 MB again.

    Transfer Rate problem :
    The transfer rate is extremely slow and I get like 4-5 MBs/second from one local VM to my desktop.
    If I just read like 400 MB, it takes 12-13 seconds. With transferring data over the socket, it takes a whopping 80 seconds.

    One last note is that, this protocol object is created inside the main() function and hence its running on the main thread.

    Now my code is as follows :

    Qt Code:
    1. // protocol.h
    2. #ifndef PROTOCOL_H
    3. #define PROTOCOL_H
    4. #include <QtNetwork>
    5.  
    6. // myproto Thread class
    7. class Protocol : public QObject {
    8.  
    9. Q_OBJECT
    10.  
    11. public:
    12.  
    13. // Constructor
    14. Protocol(QObject *parent = 0);
    15.  
    16. // Destructor
    17. ~Protocol();
    18.  
    19. // The Socket pointer
    20. QSslSocket * socket;
    21.  
    22. // Variables for logging in etc
    23. QMap<QString, QString> vars;
    24.  
    25. unsigned int bblen; // The len of data in the bbuffer
    26.  
    27. int Compress; // Is the current partition to be compressed ?
    28. unsigned int written; // How many packets have been written
    29. unsigned int maxtx; // The amount of packets that can be written in one shot
    30. int socket_rw_time; // The last time there was a socket read write
    31. int sleepFor; // Will sleep for these many milliseconds
    32. int doneCalled; // Is set to 1 or greater when done is called
    33. QList<quint64> badBlocks; // Any bad blocks found
    34.  
    35. // Finish the backup process
    36. void done();
    37.  
    38. // Write the data with the length header
    39. qint64 rite (const QByteArray & data, bool keptAlive = false);
    40.  
    41. // Start the process to backup - Issues the login command
    42. bool doit();
    43.  
    44. // Send Data
    45. void sendBlocks();
    46.  
    47. signals:
    48.  
    49. void quit();
    50.  
    51. void finished();
    52.  
    53. private slots:
    54.  
    55. // When any data is available, it goes through this
    56. void socketReadyRead();
    57.  
    58. // Called when the Encryption starts over SSL Sockets
    59. void socketEncrypted();
    60.  
    61. // More slots
    62.  
    63. };
    64.  
    65. #endif
    To copy to clipboard, switch view to plain text mode 

    Qt Code:
    1. // protocol.cpp
    2. #include "protocol.h"
    3.  
    4. // The constructor
    5. Protocol::Protocol(QObject *parent) : QObject(parent) {
    6.  
    7. NULOG2("[Protocol::Protocol] Start");
    8.  
    9. // Codes
    10.  
    11. socket = new QSslSocket();
    12. }
    13.  
    14. // Destructor
    15. Protocol::~Protocol(){
    16.  
    17. NULOG2("[Protocol::~Protocol] Destructor");
    18.  
    19. delete socket;
    20.  
    21. }
    22.  
    23.  
    24. // Finish the backup process, even in case of error
    25. void Protocol::done(){
    26.  
    27. // Had to comment due to large post
    28.  
    29. }
    30.  
    31. // Socket write safe as it appends the length to the beginning of the string
    32. qint64 Protocol::rite(const QByteArray & data, bool keptAlive){
    33. quint16 qlen = ((quint16)data.size());
    34.  
    35. len.append(QByteArray(((char*)&qlen)+1,1));
    36. len.append(QByteArray((char*)&qlen,1));
    37.  
    38. qint64 w = socket->write(len + data);
    39. NULOG4("[Protocol::rite] Size : " << data.size() << "Written : " << w);
    40.  
    41. return w;
    42. }
    43.  
    44. // Start the whole process
    45. bool Protocol::doit(){
    46.  
    47. // Get the kernel data
    48. //this->myproto_config();
    49.  
    50. connect(socket, SIGNAL(readyRead()),
    51. this, SLOT(socketReadyRead()));
    52. connect(socket, SIGNAL(bytesWritten(qint64)),
    53. this, SLOT(socketBytesWritten(qint64)));
    54. connect(socket, SIGNAL(encryptedBytesWritten(qint64)),
    55. this, SLOT(socketencryptedBytesWritten(qint64)));
    56. connect(socket, SIGNAL(sslErrors(QList<QSslError>)),
    57. this, SLOT(sslErrors(QList<QSslError>)));
    58. connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
    59. this, SLOT(serror(QAbstractSocket::SocketError)));
    60. /*connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
    61. this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));*/
    62.  
    63. // Encrypted
    64. if(true){
    65.  
    66. NULOG1("[Protocol::doit] Connecting to Secure Server " << vars["hostname"] << "Port :" << vars["port"]);
    67.  
    68. connect(socket, SIGNAL(encrypted()),
    69. this, SLOT(socketEncrypted()));
    70.  
    71. // Connect to the server
    72. socket->connectToHostEncrypted(vars["hostname"], vars["port"].toInt());
    73.  
    74. }
    75. return true;
    76.  
    77. }
    78.  
    79. // Called when the connection is fully established
    80. void Protocol::socketEncrypted(){
    81.  
    82. NULOG1("[Protocol::socketEncrypted] Connection Established with Server");
    83.  
    84. // This is the myproto protocol
    85. socket->write("myproto\r\n");
    86. socket->waitForBytesWritten();
    87.  
    88. QByteArray username = vars["username"].toUtf8();
    89. QByteArray password = vars["password"].toUtf8();
    90.  
    91. // We first need to LOGIN
    92. this->rite("LOGIN "+ username.toBase64() +" "+ password.toBase64());
    93.  
    94. }
    95.  
    96. // Called whenever there is something to READ in the buffer
    97. void Protocol::socketReadyRead(){
    98.  
    99. QByteArray buf; // The buffer of the socket read
    100. qint64 n;
    101. QString str;
    102. int Packetlen;
    103.  
    104. while(socket->bytesAvailable() != 0){
    105.  
    106. // Find the length of the command
    107. if (dlen == 0) {
    108.  
    109. if(socket->bytesAvailable() < 2){
    110. NULOG2("[Protocol::socketReadyRead] Less than 2 packets...breaking" << socket->bytesAvailable());
    111. break;
    112. }
    113.  
    114. len = socket->read(2);
    115.  
    116. // Was there an error in reading ?
    117. if(len.size() != 2){
    118. NULOG0("[Protocol::socketReadyRead] Could not read the Length of the Packet " << len.size());
    119. return;
    120. }
    121.  
    122. dlen = (qint64)((((quint8)len[0]) << 8) | (quint8)len[1]);
    123.  
    124. // The packet cannot be greater than buffer
    125. if(dlen > 8192){
    126. NULOG0("[Protocol::socketReadyRead] The packet cannot be greater than buffer");
    127. return;
    128. }
    129.  
    130. }
    131.  
    132. // If there are not many bytes available then break
    133. if(socket->bytesAvailable() < dlen){
    134. break;
    135. }
    136.  
    137. buf = socket->read(dlen);
    138.  
    139. // Is the packet of the right size
    140. if(buf.size() != ((int)dlen)){
    141. NULOG0("[Protocol::socketReadyRead] The bytes read" << n << "dont match the packet size" << dlen);
    142. return;
    143. }
    144.  
    145. // Clear the string
    146. str = buf;
    147.  
    148. // Reset the counter
    149. Packetlen = (int)dlen;
    150. dlen = (qint64)0;
    151.  
    152. // Send data
    153. if (str[0].toLower() == QChar('gd')){
    154.  
    155. sendBlocks();
    156.  
    157. // Quit
    158. }else if (str[0].toLower() == QChar('qu')){
    159.  
    160. done();return;
    161.  
    162. // Unknown Command
    163. }else{
    164.  
    165. NULOG0("[Protocol::socketReadyRead] An Unknown Command has happened");
    166.  
    167. }
    168.  
    169. }// End of while
    170.  
    171. }
    172.  
    173. // Sends the data to the server
    174. void Protocol::sendBlocks(){
    175.  
    176. // Open File
    177. QFile fp("/path/to/large/file");
    178.  
    179. if(!fp.open(QIODevice::ReadOnly)){
    180. return false;
    181. }
    182.  
    183. qint64 read = 0;
    184.  
    185. while(fp.size() != read){
    186.  
    187. QByteArray finalData = fp.read(4096);
    188.  
    189. // Write data
    190. this->rite(finalData);
    191.  
    192. this->bblen++;
    193.  
    194. // Sleep to reduce read load
    195. if((this->bblen % 2500) == 0 && this->bblen > 0){
    196.  
    197. NULOG2("[Protocol::sendBlocks] Bytes " << this->socket->encryptedBytesToWrite() << this->socket->bytesToWrite());
    198.  
    199. /*// The following CODE is used to flush data as for some reason the data is not flushed automatically
    200. if((this->bblen % 25000) == 0){
    201.  
    202. while(this->socket->encryptedBytesToWrite() > 10000000 || this->socket->bytesToWrite() > 10000000){
    203. this->socket->flush();
    204. }
    205.  
    206. }*/
    207.  
    208. }
    209.  
    210. }
    211.  
    212. // Wait for more socket read !
    213.  
    214. return true;
    215.  
    216. }
    To copy to clipboard, switch view to plain text mode 

    Qt Code:
    1. // Code to execute protocol in main.cpp
    2. int main(){
    3. Protocol protObj;
    4. protObj.doit();
    5. }
    To copy to clipboard, switch view to plain text mode 

  2. #2
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QSslSocket/QTcpSocket not flushing data by itself

    Your problem seems to be that you try to send all the data at once using write(), which simply enqueues the data in an unbounded buffer. Data will only be sent when you return to the event loop and calling flush() is a poor attempt at fixing a broken design. I suggest you take an asynchronous approach, just like you would use to read data.

    You could add members to your class to keep track of how much data you have sent from your file. Simply making the QFile a member of the class may be enough. Then, connect the socket's encryptedBytesWritten() signal to a slot in your class, in which you first call encryptedBytesToWrite() to check how much data is yet to be sent and, if this number goes below a certain threshold, read some chunk of data from the file, write() it to the socket, and return to let control go back to the event loop and have the socket send whatever is waiting in its buffer. That way, you will keep memory usage under control by loading the file on demand as the data is sent over the network.

    Note that this asynchronous design will spread through the program: instead of the current (synchronous) sendBlocks() method, you will need a method which starts sending data and returns immediately, and a signal that you emit when the work is done (emit this signal from within the slot connected to encryptedBytesWritten(), after reaching the end of, then closing, the file).

    By the way, your program does connect encryptedBytesWritten() to some slot whose code your forgot to include in your snippet.

  3. #3
    Join Date
    Dec 2009
    Location
    New Orleans, Louisiana
    Posts
    791
    Thanks
    13
    Thanked 153 Times in 150 Posts
    Qt products
    Qt5
    Platforms
    MacOS X

    Default Re: QSslSocket/QTcpSocket not flushing data by itself

    Quote Originally Posted by raheelgup View Post
    Transfer Rate problem :
    The transfer rate is extremely slow and I get like 4-5 MBs/second from one local VM to my desktop.
    If I just read like 400 MB, it takes 12-13 seconds. With transferring data over the socket, it takes a whopping 80 seconds.
    You are definitely slowing things down by calling waitForBytesWritten. Since you are running this all on the main thread, you're causing the main thread to block, which prevents you from reading more data to send. I also see that you are reading data in pretty small chunks of 4096 bytes and writing to the socket the same 4096 byte chunks. You should increase the amount of data read from the file in one read operation and write the same amount of data to the socket. You can benchmark different values to determine the optimal read/write values, etc. Then change your Protocol::socketReadyRead() slot to ReadAll into a QByteArray and append the new data read to the existing QByteArray buffer. You should also make the QByteArray buf a class variable so that its contents can persist across calls to Protocol::socketReadyRead() because Protocol::socketReadyRead() will not always have all bytes sent available at once (in fact, it rarely does when reading/writing lots of data).

    Parse the "records" that may be in the QByteArray by looking at the length of data you're prepending in Protocol::rite() and determining whether or not you have a complete data item. If you do, extract that data from the QByteArray, then remove that data from QByteArray using QByteArray::remove(0, len) so that it's no longer in the buffer and the next "record" is at position 0 in the QByteArray, etc.

    One more comment, I don't see where you are writing the commands 'gd' and 'qu' to the socket in your code, but in Protocol::socketReadyRead() you don't seem to be expecting them to be proceeded with the length of the command data item. I would suggest that you be consistent and each packet of data written by your app should include some common prefix like a length of the data, the "command", followed by any data specific to the "command" type (could be nothing, etc). You may also consider using a command like 'dt' (or any other unique value you want) to denote this is file data, etc. This way you can consistently determine what the packet of data is to be used for, etc.

    You may also consider some type of checksum or hash value of the data so that you can verify the contents on the receiving end. You'll have the length of the data and a checksum/hash that you could compute on the received data to ensure it matches the data that was sent. This is not uncommon in backing up files across a network. You're better off finding out that what you received doesn't match what was sent at the time the data is received, so you could implement a new command (like 'rt' for retry) to retransmit that block of data, etc.

    Hope this gets you pointed in the right direction.

    Regards,

    Jeff

Similar Threads

  1. Conver QtcpSocket to QSslSocket
    By k.qasempour in forum Newbie
    Replies: 1
    Last Post: 6th November 2012, 22:11
  2. QSslSocket to much data?
    By Qiieha in forum Qt Programming
    Replies: 21
    Last Post: 1st February 2012, 23:56
  3. How to clear/empty data from a QSslSocket
    By scieck in forum Qt Programming
    Replies: 1
    Last Post: 8th May 2011, 00:47
  4. QSslSocket vs QTcpSocket problem
    By camol in forum Qt Programming
    Replies: 56
    Last Post: 24th March 2011, 15:32
  5. QTCPSocket not getting all data
    By jhowland in forum Qt Programming
    Replies: 4
    Last Post: 29th January 2010, 16:20

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.