PDA

View Full Version : QTcpSocket, QTcpServer problem



frido
1st April 2009, 13:04
I took threaded fortune server example (http://doc.trolltech.com/4.5/network-threadedfortuneserver.html) for experimenting with tcp socket communication.

The idea is to make a server that will accept many clients. When a connected client sends message to server, the server would sent this message to all other connected clients.

Here is the code so far:
server.h


#ifndef SERVER_H
#define SERVER_H

#include <QStringList>
#include <QTcpServer>

class Server : public QTcpServer
{
Q_OBJECT

public:
Server ( QObject *parent = 0 );

protected:
void incomingConnection ( int socketDescriptor );

private slots:
void fromClient(QByteArray data);

signals:
void toClient(QByteArray data);
};
#endif

server.cpp:


#include "server.h"
#include "serverthread.h"

Server::Server ( QObject *parent )
: QTcpServer ( parent )
{
listen ( QHostAddress::Any, 8888 );
}

void Server::incomingConnection ( int socketDescriptor )
{
ServerThread *thread = new ServerThread ( socketDescriptor, this );
connect ( thread, SIGNAL ( finished() ), thread, SLOT ( deleteLater() ) );
connect ( thread, SIGNAL ( started() ), thread, SLOT ( threadStarted() ) );
connect ( thread, SIGNAL ( fromClient ( QByteArray ) ), this, SLOT ( fromClient ( QByteArray ) ) );
connect ( this, SIGNAL ( toClient ( QByteArray ) ), thread, SLOT ( toClient ( QByteArray ) ) );
thread->start();
}

void Server::fromClient ( QByteArray data )
{
ServerThread* thread = static_cast<ServerThread*> ( sender() );
disconnect ( this, SIGNAL ( toClient ( QByteArray ) ), thread, SLOT ( toClient ( QByteArray ) ) );
emit toClient ( data );
connect ( this, SIGNAL ( toClient ( QByteArray ) ), thread, SLOT ( toClient ( QByteArray ) ) );
}

serverthread.h:


#ifndef SERVERTHREAD_H
#define SERVERTHREAD_H

#include <QThread>
#include <QTcpSocket>

class ServerThread : public QThread
{
Q_OBJECT

public:
ServerThread ( int socketDescriptor, QObject *parent );
~ServerThread ();

void run();

signals:
void error ( QTcpSocket::SocketError socketError );
void fromClient(QByteArray data);

public slots:
void toClient(QByteArray data);

private slots:
void socketDisconnected();
void receiveMessage();
void threadStarted();

private:
int socketDescriptor;
QTcpSocket* client;
quint16 blockSize;

QByteArray createQByteArray(QString str);
};
#endif

serverthread.cpp


#include "serverthread.h"

#include <QtNetwork>

ServerThread::ServerThread ( int socketDescriptor, QObject *parent )
: QThread ( parent ), socketDescriptor ( socketDescriptor )
{
blockSize = 0;
}

ServerThread::~ServerThread ( )
{
}

void ServerThread::run()
{
exec();
}

void ServerThread::socketDisconnected()
{
quit();
}

void ServerThread::threadStarted()
{
client = new QTcpSocket(this);

connect ( client, SIGNAL ( disconnected() ),
this, SLOT ( socketDisconnected() ) );

connect ( client, SIGNAL ( readyRead() ),
this, SLOT ( receiveMessage() ) );

if ( !client->setSocketDescriptor ( socketDescriptor ) )
{
emit error ( client->error() );
return;
}
}

void ServerThread::receiveMessage()
{
QDataStream in ( client );
in.setVersion ( QDataStream::Qt_4_0 );

if ( blockSize == 0 )
{
if ( client->bytesAvailable() < ( int ) sizeof ( quint16 ) )
return;

in >> blockSize;
}
if ( client->bytesAvailable() < blockSize )
return;

QString str;
QByteArray tmp;
in >> tmp;
str = QString::fromUtf8 ( qUncompress ( tmp ) );

blockSize = 0;

emit fromClient ( createQByteArray ( str ) ); //pošljemo vsem ostalim clientom povezanim na server
}

QByteArray ServerThread::createQByteArray ( QString str )
{
QByteArray block;
QDataStream out ( &block, QIODevice::WriteOnly );
out.setVersion ( QDataStream::Qt_4_0 );

out << ( quint16 ) 0;
out << qCompress ( str.toUtf8(),9 );
out.device()->seek ( 0 );
out << ( quint16 ) ( block.size() - sizeof ( quint16 ) );

return block;
}

void ServerThread::toClient ( QByteArray data )
{
client->write ( data );
}

Everithing works well if client sends one message. But if i try to send let say 1000 messages (in for loop), other clients don't receive all messages but random noumber of them. Also all clients don't receive same number of messages.

Any suggestions?

tnx

frido
3rd April 2009, 08:27
One more thing...

I added counter in ServerThread::receiveMessage() slot. I figured out that this slot is not triggered as many times as client sends the message.

I'm still pulling my hair... Help pls!

talk2amulya
3rd April 2009, 21:29
perhaps u could use QSignalSpyand with its api, urself make sure that the slot is called as no. of times as wanted..i guess what is happening here is when the same slot is left to be processed for n no. of times, Qt executes only a couple of times and assumes its done..i m not sure though about that though

frido
3rd April 2009, 23:16
I found the problem (and solution)!

This post (http://lists.trolltech.com/qt-interest/2006-03/thread01624-0.html) was very helpful. Especially this part:

Qt can only wait until the data has been written to the device. The device is
the TCP/IP protocol stack of the operating system, not the network wires.

From that time on it is absolutely out of control of Qt. Since you send just
20 bytes of data this will probably be accepted by the OS immediatly, making
waitForBytesWritten return. After that there will only be some microseconds
in your for-loop when the next bytes get written. So your OS might decide to
pack all these bytes together and send it as one packet. This will at least
happen with the second and third msg since the first msg is still keeping the
network wires busy.
Even if your msgs are send as you write them the receiver might put them all
together into one big chunk.


So I changed receiveMessage() a bit assuming kernel could combine many messages together.



void ServerThread::receiveMessage()
{
QDataStream in ( this );
in.setVersion ( QDataStream::Qt_4_0 );

while ( !in.atEnd() )
{
if ( blockSize == 0 )
{
if ( bytesAvailable() < ( int ) sizeof ( quint16 ) )
return;

in >> blockSize;
}

if ( bytesAvailable() < blockSize )
return;

QString str;
QByteArray tmp;
in >> tmp;
str = QString::fromUtf8 ( qUncompress ( tmp ) );

blockSize = 0;
emit fromClient ( createQByteArray ( str ) );
}
}


So... Fortune Client/Server Example works fine as long as you send only one message or many messages at slow speed (pause between messages). If you send messages at high speed (like for loop), the kernel combines them. And you should be prepared for that situation. I think TrollTech should mention it somewhere in example or documentation.

Thanks everyone for help!