通过网络发送结构化数据

0 投票
2 回答
1015 浏览
提问于 2025-04-18 06:05

我刚开始学习网络编程,所以如果我的问题听起来有点简单,请见谅。

我正在尝试从一个Qt应用程序向一个Python服务器发送一些数据,服务器会处理这些数据并返回一些答案。

QTcpSocket类中,有一些方法可以让我发送数据:

// ...
write(const QByteArray &)
write(const char *)
// ...

我的应用程序需要处理:身份验证、发送和接收一些复杂的数据,比如struct和文件。

我对这种情况有很多问题:

  1. 上面提到的方法足够用来发送复杂数据吗?怎么做呢?
  2. 在服务器端(使用Python)如何处理数据类型?
  3. 你觉得我应该使用其他协议,比如HTTP(使用QNetworkAccessManager类)吗?

2 个回答

1

我觉得不需要区分编程语言的数据结构类型,关键在于你发送的数据。不同的编程语言可能有不同的结构,这些都是比较底层的细节。更重要的是你实际发送了什么。

你可以看看下面这个例子,了解在QtCore中如何使用json格式进行序列化和反序列化。Python的json模块也支持得很好,所以在服务器端处理这些数据不会有问题:

JSON 保存游戏示例

这个部分基本上会给你一些关于客户端的提示。不要被保存到文件的过程搞混了。其实就是把原始数据写入文件,而你可以通过网络发送这些数据:

void Game::write(QJsonObject &json) const
{
    QJsonObject playerObject;
    mPlayer.write(playerObject);
    json["player"] = playerObject;

    QJsonArray levelArray;
    foreach (const Level level, mLevels) {
        QJsonObject levelObject;
        level.write(levelObject);
        levelArray.append(levelObject);
    }
    json["levels"] = levelArray;
}

... 然后在服务器端你会做类似的事情,当然不是从文件读取,而是从网络读取,但这并不复杂,因为两者都是输入输出操作。

import json
json_data=open(file_directory).read()

data = json.loads(json_data)
pprint(data)

你可以使用原始协议来设计自己的协议,或者直接使用扩展。我建议使用一些标准的协议,比如http(tcp/udp)。这样你只需要定义自己的数据的json格式,而不需要处理其他的事情,比如单向或双向通信、事务标识符防止回复攻击、时间戳、数据大小等等。

这样你就可以真正专注于对你来说重要的内容。一旦你定义了自己的json格式,你可以查看QtNetwork模块,根据需要发送post、get、put和delete请求。

你可能会与QNetworkManagerQNetworkReply类紧密合作。在这里你可以找到一个简单的Qt客户端实现,使用QtCore的json来实现一个简单的pastebin功能:

#include <QSslError>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QTcpSocket>

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QFile>
#include <QScopedPointer>
#include <QTextStream>
#include <QStringList>
#include <QCoreApplication>

#include <QDebug>

int main(int argc, char **argv)
{
    QCoreApplication application{argc, argv};
    application.setOrganizationName(R"("CutePaste")");
    application.setApplicationName(R"("CutePaste Desktop Console Frontend")");

    QTextStream standardOutputStream{stdout};
    QFile dataFile;
    QString firstArgument{QCoreApplication::arguments().size() < 2 ? QString() : QCoreApplication::arguments().at(1)};
    if (!firstArgument.isEmpty()) {
        dataFile.setFileName(firstArgument);
        dataFile.open(QIODevice::ReadOnly);
    } else {
        dataFile.open(stdin, QIODevice::ReadOnly);
    }

    QByteArray pasteTextByteArray{dataFile.readAll()};

    QJsonObject requestJsonObject;
    requestJsonObject.insert(QStringLiteral("data"), QString::fromUtf8(pasteTextByteArray));
    requestJsonObject.insert(QStringLiteral("language"), QStringLiteral("text"));

    QJsonDocument requestJsonDocument{requestJsonObject};

    QString baseUrlString{QStringLiteral(R"("http://pastebin.kde.org")")};

    QNetworkRequest networkRequest;
    networkRequest.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, true);
    networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, R"("application/json")");
    networkRequest.setUrl(QUrl(baseUrlString + R"("/api/json/create")"));

    QNetworkAccessManager networkAccessManager;
    QScopedPointer<QNetworkReply> networkReplyScopedPointer(networkAccessManager.post(networkRequest, requestJsonDocument.toJson()));
    QObject::connect(networkReplyScopedPointer.data(), &QNetworkReply::finished, [&] {

        QJsonParseError jsonParseError;
        QByteArray replyJsonByteArray{networkReplyScopedPointer->readAll()};
        QJsonDocument replyJsonDocument{QJsonDocument::fromJson(replyJsonByteArray, &jsonParseError)};
        if (jsonParseError.error != QJsonParseError::NoError) {
            qDebug() << R"("The json network reply is not valid json:")" << jsonParseError.errorString();
            QCoreApplication::quit();
        }

        if (!replyJsonDocument.isObject()) {
            qDebug() << R"("The json network reply is not an object")";
            QCoreApplication::quit();
        }

        QJsonObject replyJsonObject{replyJsonDocument.object()};
        QJsonValue resultValue{replyJsonObject.value(QStringLiteral("result"))};

        if (!resultValue.isObject()) {
            qDebug() << R"("The json network reply does not contain an object for the "result" key")";
            QCoreApplication::quit();
        }

        QJsonValue identifierValue{resultValue.toObject().value(QStringLiteral("id"))};

        if (!identifierValue.isString()) {
            qDebug() << R"("The json network reply does not contain a string for the "id" key")";
            QCoreApplication::quit();
        }

        endl(standardOutputStream << baseUrlString << '/' << identifierValue.toString());

        QCoreApplication::quit();
    });

    QObject::connect(networkReplyScopedPointer.data(), static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [&](QNetworkReply::NetworkError networkReplyError) {
        if (networkReplyError != QNetworkReply::NoError)
            endl(standardOutputStream << networkReplyScopedPointer->errorString());
    });

    QObject::connect(networkReplyScopedPointer.data(), &QNetworkReply::sslErrors, [&](QList<QSslError> networkReplySslErrors) {
        if (!networkReplySslErrors.isEmpty()) {
            for (const auto &networkReplySslError : networkReplySslErrors)
                endl(standardOutputStream << networkReplySslError.errorString());
        }
    });

    int returnValue{application.exec()};

    dataFile.close();
    if (dataFile.error() != QFileDevice::NoError)
        endl(standardOutputStream << dataFile.errorString());

    return returnValue;
}

这里定义了JSON格式:

http://sayakb.github.io/sticky-notes/pages/api/

当然,这并不是唯一的方法,比如如果你需要效率,可以考虑使用像capnproto这样的二进制格式。

3

我来回答你的问题:

上面提到的方法足够用来发送复杂数据吗?怎么做呢?

是的,发送原始的字节数组是最基本的方式。不过,你需要一个东西,能够把你的复杂数据转换成字节数组,并且能把字节数组再转换回复杂数据。

这个过程有很多不同的叫法,比如编码、序列化、封送等……但总的来说,就是创建一个系统,把复杂的结构转化为一串字节或字符。

你可以选择很多种方式,比如ASN.1JSONXML谷歌的协议缓冲,或者MIME……

你甚至可以自己设计一种方式(例如,使用TLV:标签-长度-值的简单方案,其中标签是类型的标识符,值可以是基本类型[你需要为每种你认为是基本的类型定义一种表示方式],或者是一个或多个TLV),长度表示编码值所用的字节/字符数。

选择哪种方式很大程度上取决于你在哪里进行编码(语言/平台)和在哪里进行解码(语言/平台),以及你对速度、带宽使用、传输、是否需要检查消息等的要求。

如果你在处理不同架构的数据,可能还需要考虑字节序的问题。

最后,你需要区分格式(即复杂结构如何以字节序列的形式表达)和用于编码的库(或用于解码的库)。有时候它们是相关的,有时候对于同一种格式,你可以选择不同的库。

在服务器端(使用Python)如何处理数据类型?

这里有一个要求……如果你选择使用外部提供的格式,必须确保有一个能够解码它的Python库。

如果你选择自制解决方案,你需要定义的是如何将复杂的C++结构表示为Python结构。

另外一个可能性是全部使用C++,然后在Python服务器端使用某种创建Python扩展的系统(例如boost-pythonswig……)

你觉得我应该使用其他协议,比如HTTP(使用QNetworkAccessManager类)吗?

这要看你想做什么。 有很多HTTP库可以在不同的语言和架构上使用。

你仍然需要解决信息格式化的问题(虽然HTTP有一些定义好的实践)。

此外,HTTP明显是偏向于客户端与服务器之间的通信,动作总是由客户端发起。

当服务器需要发起通信或发送突发信息时,事情就变得复杂(或者支持得不那么广泛)。

撰写回答