Qt, отправка нескольких типов данных с клиента на сервер + потоковая передача данных

У меня есть приложение Qt на основе клиент/сервер, использующее QTcpServer и QTcpSocket, мне удалось установить соединение и отправить некоторые данные туда и обратно между клиентом и сервером. Клиент отправляет на сервер множество типов данных (строка, целое число, файлы и аудиопоток в реальном времени), и, поскольку мой сервер использует один слот для ввода данных (readyRead()):

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

Я не знаю, как я мог различать все эти полученные данные и вызывать соответственно правильную функцию на сервере.

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
- ...

СЕРВЕР:

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()
{
    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:
        // ...    
    }
}

Я не ищу полного решения, все, что я ищу, это некоторое руководство в правильном направлении.


person mark marich    schedule 25.08.2016    source источник
comment
Похоже, вам нужно изобрести (или использовать существующий) протокол, который мог бы сказать вам, какие данные вы передаете.   -  person Some programmer dude    schedule 25.08.2016
comment
Я не могу найти в сети ни одного примера того, как я могу это сделать.   -  person mark marich    schedule 25.08.2016
comment
Как минимум, вы можете упаковать тип и значение в сообщение, а затем на принимающей стороне включить тип.   -  person Kuba hasn't forgotten Monica    schedule 25.08.2016
comment
Вы уверены, что не можете найти ничего, например. как мне создать свой собственный протокол связи? У меня много хитов. Или почему бы не использовать существующий протокол, например, например, HTTP?   -  person Some programmer dude    schedule 25.08.2016
comment
Не рекомендуется передавать поток в реальном времени через TCP. Вы можете столкнуться с проблемой задержки. Лучше использовать UDP. Возможно, лучше сделать аудиосервис отдельно по UDP.   -  person Kirill Chernikov    schedule 25.08.2016
comment
Я думал о том, чтобы упаковать тип и значение в сообщение, но это не сработает с потоком аудио!   -  person mark marich    schedule 25.08.2016
comment
@JoachimPileborg, как вы думаете, в этом случае лучше использовать HTTP?   -  person mark marich    schedule 25.08.2016
comment
Почему это не будет работать для аудиопотока? Потому что вы не знаете окончательный размер объекта, который вы передаете?   -  person drescherjm    schedule 25.08.2016
comment
Возможно, лучше сделать аудиосервис отдельно по протоколу UDP. Это идея. Для аудио вы можете отправить команду по TCP и попросить сервер настроить поток по UDP на другой порт.   -  person drescherjm    schedule 25.08.2016
comment
@drescherjm, если честно, я не проверял это, но я думал, что невозможно узнать, где заканчивается и начинается поток, если я оберну поток данных строкой типа, кроме того, я не знаю, как все это объединить (аудио поток + тип) на стороне клиента.. Что вы думаете?   -  person mark marich    schedule 25.08.2016
comment
это не будет работать со звуковым потоком. Аудиопоток представляет собой отдельный объект. Вы отправляете URL-адрес потока, а затем запускаете поток отдельно. Не думайте о типах как о типах C++. Тип действительно пакетный. У вас может быть пакет, который означает URL-адрес аудиопотока. Для потоковой передачи аудио вы, вероятно, захотите повторно использовать существующий сервер потоковой передачи аудио. Если нет, вам придется свернуть свой собственный, используя UDP, но вам все равно нужно иметь средства для обмена адресами хостов и номерами портов, а также для того, чтобы клиент мог выполнять обход маршрутизатора. Скорее всего, подойдет существующая библиотека.   -  person Kuba hasn't forgotten Monica    schedule 25.08.2016


Ответы (1)


Реализация вашего протокола не полностью использует QDataStream в Qt 5.7. Вот как это может выглядеть сейчас - это может быть довольно просто.

Во-первых, давайте определим известные нам запросы:

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

Также было бы удобно иметь помощника по транзакциям RAII.

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

Клиент получает запросы от сервера и сигнализирует о необходимости ответить данными. Код, использующий клиента, будет реагировать на эти сигналы и отвечать, вызывая соответствующий слот. Например.

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

А также:

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

Сервер очень похож. Его пользователь отправляет запрос клиенту через request слотов. Как только сервер получает ответ от клиента, он указывает это с помощью сигналов has:

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;
    }
};
person Kuba hasn't forgotten Monica    schedule 25.08.2016