Qt, Sending multiple data types from client to ser

2020-02-11 17:53发布

问题:

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.

回答1:

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;
    }
};