通过网络发送结构化数据
我刚开始学习网络编程,所以如果我的问题听起来有点简单,请见谅。
我正在尝试从一个Qt应用程序向一个Python服务器发送一些数据,服务器会处理这些数据并返回一些答案。
在QTcpSocket
类中,有一些方法可以让我发送数据:
// ...
write(const QByteArray &)
write(const char *)
// ...
我的应用程序需要处理:身份验证、发送和接收一些复杂的数据,比如struct
和文件。
我对这种情况有很多问题:
- 上面提到的方法足够用来发送复杂数据吗?怎么做呢?
- 在服务器端(使用Python)如何处理数据类型?
- 你觉得我应该使用其他协议,比如HTTP(使用
QNetworkAccessManager
类)吗?
2 个回答
我觉得不需要区分编程语言的数据结构类型,关键在于你发送的数据。不同的编程语言可能有不同的结构,这些都是比较底层的细节。更重要的是你实际发送了什么。
你可以看看下面这个例子,了解在QtCore中如何使用json格式进行序列化和反序列化。Python的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请求。
你可能会与QNetworkManager、QNetworkReply类紧密合作。在这里你可以找到一个简单的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这样的二进制格式。
我来回答你的问题:
上面提到的方法足够用来发送复杂数据吗?怎么做呢?
是的,发送原始的字节数组是最基本的方式。不过,你需要一个东西,能够把你的复杂数据转换成字节数组,并且能把字节数组再转换回复杂数据。
这个过程有很多不同的叫法,比如编码、序列化、封送等……但总的来说,就是创建一个系统,把复杂的结构转化为一串字节或字符。
你可以选择很多种方式,比如ASN.1、JSON、XML、谷歌的协议缓冲,或者MIME……
你甚至可以自己设计一种方式(例如,使用TLV:标签-长度-值的简单方案,其中标签是类型的标识符,值可以是基本类型[你需要为每种你认为是基本的类型定义一种表示方式],或者是一个或多个TLV),长度表示编码值所用的字节/字符数。
选择哪种方式很大程度上取决于你在哪里进行编码(语言/平台)和在哪里进行解码(语言/平台),以及你对速度、带宽使用、传输、是否需要检查消息等的要求。
如果你在处理不同架构的数据,可能还需要考虑字节序的问题。
最后,你需要区分格式(即复杂结构如何以字节序列的形式表达)和用于编码的库(或用于解码的库)。有时候它们是相关的,有时候对于同一种格式,你可以选择不同的库。
在服务器端(使用Python)如何处理数据类型?
这里有一个要求……如果你选择使用外部提供的格式,必须确保有一个能够解码它的Python库。
如果你选择自制解决方案,你需要定义的是如何将复杂的C++结构表示为Python结构。
另外一个可能性是全部使用C++,然后在Python服务器端使用某种创建Python扩展的系统(例如boost-python或swig……)
你觉得我应该使用其他协议,比如HTTP(使用QNetworkAccessManager类)吗?
这要看你想做什么。 有很多HTTP库可以在不同的语言和架构上使用。
你仍然需要解决信息格式化的问题(虽然HTTP有一些定义好的实践)。
此外,HTTP明显是偏向于客户端与服务器之间的通信,动作总是由客户端发起。
当服务器需要发起通信或发送突发信息时,事情就变得复杂(或者支持得不那么广泛)。