PDA

View Full Version : Server sent message not reaching client



tiredtyrant
28th February 2014, 21:11
I have two projects, one for the server and the other for the client:

Server:


int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

Server s;
s.start(9999);

return a.exec();
}

class Server : public QObject
{
Q_OBJECT
public:
explicit Server(QObject *parent = 0);

signals:

public slots:
void start(int port);
void newConnection();
void sendMsg();

private:
QTcpServer m_server;
QList<QTcpSocket*> m_clientList;
QTimer m_timer;

};

Server::Server(QObject *parent) :
QObject(parent)
{
QObject::connect(&m_server,SIGNAL(newConnection()),this,SLOT(newConn ection()));
QObject::connect(&m_timer,SIGNAL(timeout()),this,SLOT(sendMsg()));
m_timer.setInterval(1000);
m_timer.start();
}

void Server::start(int port)
{
m_server.listen(QHostAddress::Any,port);
}

void Server::newConnection()
{
qDebug() << "NEW CONNECTION!";
m_clientList.append(m_server.nextPendingConnection ());
qDebug() << m_clientList.count();
}

void Server::sendMsg()
{
foreach(QTcpSocket* socket, m_clientList)
{
socket->write("test");
qDebug() << "send: " << socket->peerAddress();
}
}

Client:


int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

Client c;
c.connectToHost("localhost",9999);

return a.exec();
}

class Client : public QObject
{
Q_OBJECT
public:
explicit Client(QObject *parent = 0);

signals:

public slots:
void connectToHost(QString hostName, int port);
void read();

private:
QTcpSocket m_client;

};

Client::Client(QObject *parent) :
QObject(parent)
{
QObject::connect(&m_client,SIGNAL(readyRead()),this,SLOT(read()));
}

void Client::connectToHost(QString hostName, int port)
{
m_client.connectToHost(hostName,port);
}

void Client::read()
{
QDataStream in(&m_client);
in.setVersion(QDataStream::Qt_4_0);
QString msg;
in >> msg;
qDebug() << msg;
}


It should print 'test' on the client, but it prints an empty string. What am I missing?

ChrisW67
1st March 2014, 04:15
Two things.

First you are writing four raw bytes to the stream and the receiver is expecting a QDataStream containing a serialised QString. It is likely (though not guaranteed, see next point) that the four bytes arrive in one call to readyRead() and that when trying to stream a QString out of a QDataStream will result in these being treated as a string length (followed by no string data). Either both ends use QDataStream or neither does.

Secondly you are assuming payload bytes written together in one operation are sent and arrive in a single packet and trigger only a single readyRead() signal. This is not the case in general for TCP. This is dealt with by buffering data until you have a complete payload before trying to decode it.

tiredtyrant
6th March 2014, 22:00
Okay, now I'm using QDataStream on both ends. Changed Server::sendMsg() to:



void Server::sendMsg()
{
foreach(QTcpSocket* socket, m_clientList)
{
QByteArray block;
QDataStream out(&block,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << QString("test");
socket->write(block);
qDebug() << "send: " << socket->peerAddress();
}
}


Its working now. Thanks.

I assume the buffering is done by sending the block size before the message itself, like in the fortune examples? So I should always send the size first? Is it always done this way?

ChrisW67
7th March 2014, 01:46
QDataStream serialises a QString by writing a size then the bytes of the string. This size is what allows the reading QDataStream to know when this serialised string ends and the next element in the stream begins.

If you have not changed the receiver at all then you will still come to grief if you send a particularly large string that arrives in two or more packets (try sending QString(2048, 'A')). You need to make sure you have a complete message before you attempt to stream your data out of it. For binary data streams you would typically do this with your own payload size value sent before the payload (e.g. the serialised string with its size), buffer data until you have at least that many bytes, then process the payload leaving excess bytes in the buffer.

tiredtyrant
7th March 2014, 14:12
Could you help me understand the fortune client example? Here's the readFortune() code with a few of my comments and questions, can you tell me if I understood the code correctly?



void Client::readFortune()
{
QDataStream in(tcpSocket);
in.setVersion(QDataStream::Qt_4_0);

if (blockSize == 0) {
/*
If the size of the data received is less than what is required to store the block size, do nothing
*/
if (tcpSocket->bytesAvailable() < (int)sizeof(quint16))
return;

in >> blockSize;
}

/*
Is this where the buffering takes place?
bytesAvailable() return will grow each time readyRead() is emitted, until its value is equal to blockSize
*/
if (tcpSocket->bytesAvailable() < blockSize)
return;

QString nextFortune;
/*
This is where the data is read.
If we reach this part of the code, it means we have the complete data, and its safe to read and use it.
*/
in >> nextFortune;


/*
Why use QTimer and not call requestNewFortune() directly?
*/
if (nextFortune == currentFortune) {
QTimer::singleShot(0, this, SLOT(requestNewFortune()));
return;
}

currentFortune = nextFortune;
statusLabel->setText(currentFortune);
getFortuneButton->setEnabled(true);
}