c++ - Qt,从客户端发送多种数据类型到服务器+数据流

标签 c++ qt client-server qtcpsocket qtcpserver

我有一个基于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:
        // ...    
    }
}

我并不是在寻找完整的解决方案,我只是在寻找正确方向的指导。

最佳答案

您的协议(protocol)的实现没有充分利用 Qt 5.7 中的QDataStream。现在看起来是这样的 - 它可能非常简单。

首先,让我们定义我们所知道的请求:

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

客户端接收来自服务器的请求并发出需要回复数据的信号。使用客户端的代码将对这些信号使用react,并通过调用匹配的槽进行回复。例如

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

服务器非常相似。它的用户通过请求槽将请求发送到客户端。一旦服务器收到客户端的回复,它就会通过 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;
    }
};

关于c++ - Qt,从客户端发送多种数据类型到服务器+数据流,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39144556/

相关文章:

javascript - 如何使用 node.js 中的 c 头文件解析 UDP 数据包

c++ - 如何自动包含目录中的所有标题

java - android中的客户端服务器

iOS - 如何在客户端服务器聊天应用程序中正确使用后台获取和远程通知?

javascript - Firefox - WebSocket - 未建立连接

c# - 使用 c# 程序集的 c++ 项目 : how to speedup compile time?

c++ - 删除给定框内的点云

Qt multipart post问题

c++ - winapi - 更改显示器的分辨率不起作用

c++ - 在 Debug模式下构建 Qt 5.10 时 MSVC 编译器崩溃