2

I have a Client/Server based Qt application, using QTcpServer and QTcpSocket, I managed to do the connection and send some data back and forth between the client and the server. The client sends many types of data to the server (string, int, files and a real time audio stream) and since my server impliment a single data input SLOT (readyRead()):

connect(socket, SIGNAL(readyRead()),this, SLOT(readyRead()));

I don't know how could I distinguish between all this received data and call respectively the right function in the server.

Example (in the server):
- if I receive string        => call function showData(QString data);
- if I receive file          => call function saveFile(QFile file);
- if I receive audio stream  => play audio stream
- ...

SERVER:

void Server::newClientConnection()
{
    QTcpSocket *socket = server->nextPendingConnection();

    connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
    //...
}

void Server::readyRead()
{
    QTcpSocket *clientSocket = qobject_cast<QTcpSocket *>(sender());
    if (clientSocket == 0) {
        return;
    }

    QDataStream in(clientSocket);

    if (sizeMessageClient == 0)
    {
        if (clientSocket->bytesAvailable() < (int)sizeof(quint16)){
             return;
        }
        in >> sizeMessageClient;
    }

    if (clientSocket->bytesAvailable() < sizeMessageClient) {
        return;
    }

    sizeMessageClient = 0;

    in >> data;
/*
     I don't know the type of the received data !!

    - if I receive string        => call function showData(QString data);
    - if I receive file          => call function saveFile(QFile file);
    - if I receive audio stream  => play audio stream
    - ... 
*/

}

CLIENT:

Client::Client()
{
    socket = new QTcpSocket(this);
    connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));

    sizeMessageServer = 0;
}


void Client::readyRead()
{
    QDataStream in(socket);
    if (sizeMessageServer == 0)
    {
        if (socket->bytesAvailable() < (int)sizeof(quint16)) {
            return;
        }

        in >> sizeMessageServer;
    }

    if (socket->bytesAvailable() < sizeMessageServer) {
        return;
    }

    int messageReceived;
    in >> messageReceived;
    messageReceived = static_cast<int>(messageReceived);

    sizeMessageServer = 0;

    switch(messageReceived)
    {
        case 1:
            qDebug() << "send a File";
            sendFile();
            break;
        case 2:
            qDebug() << "send a string data";
            sendStringData();
            break;
        case 3:
            qDebug() << "stream audio to the server";
            streamAudioToServer();
            break;
        case n:
        // ...    
    }
}

I am not looking for a complete solution, all I am looking for is some guidance in the right direction.

  • 2
    It seems like you need to invent (or use an existing) *protocol* that can tell you what kind of data you transmit. – Some programmer dude Aug 25 '16 at 11:57
  • I can't find on the net any example of how I can make this happen .. – mark marich Aug 25 '16 at 12:34
  • 2
    At the very minimum, you could package the type and the value in a message, then on the receiving end switch on the type. – Kuba hasn't forgotten Monica Aug 25 '16 at 12:39
  • Are you sure you can't find anything for e.g. "how do i create my own communication protocol"? I got plenty of hits. Or why not use an existing protocol, like e.g [HTTP](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol)? – Some programmer dude Aug 25 '16 at 12:43
  • It's not good idea to transfer real time stream over TCP. You can face with latency problem. It's better to use UDP. Maybe it's better to do audio service separately over UDP. – Kirill Chernikov Aug 25 '16 at 12:48
  • I thought about packaging the type and the value in a message, but that won't work with the audio steaming ! – mark marich Aug 25 '16 at 13:04
  • @JoachimPileborg do you think it's better to use HTTP in this case ? – mark marich Aug 25 '16 at 13:08
  • Why won't it work for an audio stream? Because you don't know the final size of the object you are transferring? – drescherjm Aug 25 '16 at 13:08
  • 1
    ***Maybe it's better to do audio service separately over UDP.*** That is an idea. For the audio you could send a command over the TCP and ask the server to setup the stream on UDP on a different port. – drescherjm Aug 25 '16 at 13:10
  • @drescherjm to be honest I didn't test it, but I thought it's not possible to know where the stream end and start if I wrap the data stream with a type string, beside I don't know how to merge all this (audio stream + type) in the client side.. What do you think ? – mark marich Aug 25 '16 at 13:13
  • "that won't work with the audio steaming" It will. An audio stream is a separate entity. You send the stream's URL, and then run the stream separately. Don't think of types as of C++ types. The "type" is really a packet type. You can have a packet that means "Audio Stream URL". For streaming audio you'll likely want to reuse an existing audio streaming server. If not, you'll have to roll your own using UDP, but you still need to have means of exchanging the host addresses and port numbers, and for the client to be able to do router traversal. Most likely an existing library is the way to go. – Kuba hasn't forgotten Monica Aug 25 '16 at 14:20

1 Answers1

4

The implementation of your protocol doesn't fully leverage QDataStream in Qt 5.7. Here's how it might look now - it can be quite simple.

First, let's define the requests we know of:

enum class Req : quint32 {
    Unknown, String, File
};
Q_DECLARE_METATYPE(Req)
QDataStream & operator<<(QDataStream & ds, Req req) {
    return ds << (quint32)req;
}
QDataStream & operator>>(QDataStream & ds, Req & req) {
    quint32 val;
    ds >> val;
    if (ds.status() == QDataStream::Ok)
        req = Req(val);
    return ds;
}

It'd also be handy to have a transaction RAII helper.

struct Transaction {
    QDataStream & stream;
    Transaction(QDataStream & stream) : stream{stream} {
        stream.startTransaction();
    }
    ~Transaction() {
        stream.commitTransaction();
    }
    bool ok() {
        return stream.status() == QDataStream::Ok;
    }
};

The client receives requests from the server and signals the need to reply with data. The code that uses the client would react to these signals and reply back by invoking a matching slot. E.g.

void clientUser(Client & client) {
  QObject::connect(&client, &Client::needString, &client, [&]{
    client.sendString(QStringLiteral{"You got string!"});
  });

And:

class Client : public QObject {
    Q_OBJECT
    QIODevice & m_dev;
    QDataStream m_str{&m_dev};
    void onReadyRead() {
        Transaction tr{m_str};
        Req req;
        m_str >> req;
        if (!tr.ok()) return;
        if (req == Req::String)
            emit needString();
        else if (req == Req::File) {
            QString fileName;
            m_str >> fileName;
            if (!tr.ok()) return;
            emit needFile(fileName);
        }
        else emit unknownRequest(req);
    }
public:
    Client(QIODevice & dev) : m_dev{dev} {
        connect(&m_dev, &QIODevice::readyRead, this, &Client::onReadyRead);
    }
    Q_SIGNAL void unknownRequest(Req);
    Q_SIGNAL void needString();
    Q_SIGNAL void needFile(const QString & fileName);
    Q_SLOT void sendString(const QString & str) {
        m_str << Req::String << str;
    }
    Q_SLOT void sendFile(const QString & fileName, const QByteArray & data) {
        m_str << Req::File << fileName << data;
    }
};

The server is very similar. Its user sends the request to a client via request slots. Once the server hears back from the client, it indicates it through the has signals:

class Server : public QObject {
    Q_OBJECT
    QIODevice & m_dev;
    QDataStream m_str{&m_dev};
    void onReadyRead() {
        Transaction tr{m_str};
        Req req;
        m_str >> req;
        if (!tr.ok()) return;
        if (req == Req::String) {
            QString str;
            m_str >> str;
            if (!tr.ok()) return;
            emit hasString(str);
        }
        else if (req == Req::File) {
            QString fileName;
            QByteArray data;
            m_str >> fileName >> data;
            if (!tr.ok()) return;
            emit hasFile(fileName, data);
        }
        else emit hasUnknownRequest(req);
    }
public:
    Server(QIODevice & dev) : m_dev{dev} {
        connect(&m_dev, &QIODevice::readyRead, this, &Server::onReadyRead);
    }
    Q_SIGNAL void hasUnknownRequest(Req);
    Q_SIGNAL void hasString(const QString &);
    Q_SIGNAL void hasFile(const QString & name, const QByteArray &);
    Q_SLOT void requestString() {
        m_str << Req::String;
    }
    Q_SLOT void requestFile(const QString & name) {
        m_str << Req::File << name;
    }
};
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313