基于 Qt 的 XML-RPC 客户端:网络操作

前面我们已经了解了 XML-RPC 协议的具体内容,使用 Qt XML API 完成了 QVariant 与 XML 数据格式之间的转换。下面的内容就是,如何使用 Qt Network API,将我们的客户端与 XML-RPC 服务器相连接。

Qt 通过 QNetworkAccessManager 类与服务器进行通讯。我们这里就是要使用这个类。如果看看 Qt 的文档,就会发现,Qt 还提供了 QHttp 这样专门针对特定协议的网络访问类。但我们不去使用这些类,因为 Qt 5 中,这些类会被移除。为了让我们的代码尽可能适应 Qt 5,我们选用了 QNetworkAccessManager。

尽管 QNetworkAccessManager 不是一个单例,但是 Qt 文档中有这么一句:One QNetworkAccessManager should be enough for the whole Qt application. 也就是说,在我们的实际代码中,完全可以将其当做一个单例来使用。这样,我们可以创建一个单例类,将 QNetworkAccessManager 放置其中即可。这里,我们设计一个自己的 NetworkManager 类,作为这里的单例类。具体的实现细节暂不关心,先看看如何进行网络通讯。

XML-RPC 协议要求使用 POST 方法提交数据。所以我们网络操作的核心函数就是

QNetworkAccessManager::post(const QNetworkRequest & request,QIODevice * data)
这个函数接受两个参数,第一个是 QNetworkRequest 对象;第二个参数就是实际传输的数据。下面我们需要分别提供这两个参数值。

首先构建 QNetworkRequest 对象。注意,XML-RPC 协议要求 User-Agent 必须提供,数据内容类型是 XML,因此,我们使用下面的代码来构建:

QNetworkRequest NetworkManager::networkRequest(const QString &url,
const QString &userAgent) const
{
QNetworkRequest req;
req.setUrl(QUrl(url));
req.setRawHeader("User-Agent",userAgent.toAscii());
req.setHeader(QNetworkRequest::ContentTypeHeader,"text/xml");
return req;
}
第二个参数实际就是我们使用 QVariant 转换得到的 XML 格式的数据。

由于 QNetworkAccessManager 的网络操作都是异步的,所以我们需要连接 finished(QNetworkReply*) 信号获得服务器返回的数据。这里,由于我们使用的是自己封装的 NetworkManager,所以,我们直接将这个信号再次发送出去:

connect(m_mgr,SIGNAL(finished(QNetworkReply*)),
SLOT(onRequestFinished(QNetworkReply*)));
// ...
void NetworkManager::onRequestFinished(QNetworkReply *reply)
{
emit requestFinished(reply);
}
我们的客户端则需要连接到这个信号,以便进行返回值的处理:

connect(m_manager,SIGNAL(requestFinished(QNetworkReply*)),
SLOT(requestFinished(QNetworkReply*)));
// ...
void XmlRpcClient::requestFinished(QNetworkReply *reply)
{
QString response;
if(reply->error() == QNetworkReply::NoError) { // step 1
response = QString::fromUtf8(reply->readAll());
} else {
response = faultString(-32300,reply->errorString()); // ex 1
}
QVariant value;
int errCode;
QString errMessage;
QString methodName = m_requests.value(reply); // ex 2
if(XmlRpcResponse(response).parse(&value,&errCode,&errMessage)) { // step 2
emit finished(methodName,value);
} else {
emit fault(methodName,errCode,errMessage);
}
m_requests.remove(reply);
reply->deleteLater(); // step 3
}
注意来看 requestFinished() slot 中标记了 step 的三个语句。第一,如果没有错误,我们将 reply 以 UTF-8 的格式全部读取出来,赋值给一个 QString。第二,利用 XmlRpcResponse::parse() 函数对这个 QString 进行处理。第三,删除这个 reply 对象。这三个语句是整个处理的主体。下面来看看 XmlRpcResponse::parse() 是怎样的:

bool XmlRpcResponse::parse(QVariant * value,int * errCode,QString * errMessage)
{
QDomDocument doc;
QString errorMsg;
int errorLine;
int errorColumn;
if(!doc.setContent(m_response,&errorMsg,&errorLine,&errorColumn)) {
value = 0;
*errCode = NotWellFormed;
*errMessage = QString("Parse error: not well-formed at line %1: %2.")
.arg(errorLine).arg(errorMsg);
} else {
if(doc.documentElement().firstChild()
.toElement().tagName().toLower() == "params") {
QDomNode paramNode = doc.documentElement().firstChild().firstChild();
if(!paramNode.isNull()) {
*value = XmlRpcValue::fromXml(paramNode.firstChild().toElement());
}
return true;
} else if(doc.documentElement()
.firstChild().toElement().tagName().toLower() == "fault") {
QMap errors = XmlRpcValue::fromXml(doc.documentElement()
.firstChild().firstChild()
.toElement()).toMap();
value = 0;
*errCode = errors.value("faultCode").toInt();
*errMessage = errors.value("faultString").toString();
} else {
value = 0;
*errCode = InvalidXmlRpc;
*errMessage = QObject::tr("Parse error: invalid XML-RPC.");
}
}
return false;
}
XmlRpcResponse 构造函数接受一个 QString 参数,这个 QString 就是前面我们从 reply 中获取到的,也就是 XML-RPC 服务器返回的 XML 格式的字符串。这里实际就是按照 XML-RPC 协议的内容,对服务器返回的 XML 数据进行分析。我们调用了 XmlRpcValue::fromXml() 函数,从而可以利用前面所说的技术,将服务器返回的 XML 数据转换成 QVariant。

下面再次回到前面的 requestFinished() 函数。在标记了 ex 1 这行,我们构建了一个 XML 字符串。使用的代码如下所示:

QString XmlRpcClient::faultString(int code,const QString & message) const
{
QDomDocument doc;
QDomProcessingInstruction header = doc.createProcessingInstruction("xml",
"version=\"1.0\" encoding=\"UTF-8\"");
doc.appendChild(header);
QDomElement methodResponse = doc.createElement("methodResponse");
doc.appendChild(methodResponse);
QDomElement fault = doc.createElement("fault");
methodResponse.appendChild(fault);
QMap faultInfo;
faultInfo.insert("faultCode",code);
faultInfo.insert("faultString",message);
fault.appendChild(XmlRpcValue::toXml(faultInfo));
return doc.toString();
}
由此我们构建了一个错误字符串,从而能够利用 XmlRpcValue::fromXml() 函数,统一获得 QVariant 对象。

在标记了 ex 2 的这行,我们是利用了一个 QMap<QNetworkReply *,QString> 对象。这是由于,我们的网络通讯底层利用的是 QNetworkAccessManager 执行异步操作。异步操作虽然不会将界面锁死,但带来的影响是,我们无法预计数据返回的先后顺序,无法知道返回的 slot 对应的是哪个 request。这就需要我们自己记录下已经发送的函数名,所以我们的散列表以 reply 为键,以 QString 形式的函数名为值:

QNetworkReply * XmlRpcClient::request(const QString & url,
const QString methodName,
const QVariantList &params)
{
QNetworkReply * reply = m_manager->post(url,
XmlRpcRequest(methodName,params).data(),
m_userAgent);
m_requests.insert(reply,methodName);
return reply;
}
由于 Qt 保证发送时返回的 reply 和 finished(QNetworkReply*) signal 中的 reply 是同一个,因此我们根据这个便可以区别这个返回的 slot 对应的是哪一个 method。下面再来看看这个 request 是怎么实现的:

QByteArray XmlRpcRequest::data() const
{
QDomDocument doc;
QDomProcessingInstruction header = doc.createProcessingInstruction("xml",
"version=\"1.0\" encoding=\"UTF-8\"");
doc.appendChild(header);
QDomElement params = doc.createElement("params");
QDomElement param;
foreach(QVariant var,m_params){
param = doc.createElement("param");
param.appendChild(XmlRpcValue::toXml(var));
params.appendChild(param);
}
QDomElement methodName = doc.createElement("methodName");
methodName.appendChild(doc.createTextNode(m_methodName));
QDomElement methodCall = doc.createElement("methodCall");
methodCall.appendChild(methodName);
methodCall.appendChild(params);
doc.appendChild(methodCall);
return doc.toString().toUtf8();
}
其中,m_methodName 和 m_params 在构造时传入,前者是 QString 类型的远程调用的函数名,后者是 QVariantList 类型的参数列表。我们使用 XmlRpcValue::toXml() 函数,将其转换成 XML 格式。

至此,我们已经实现了简单的 XML-RPC 客户端。如果有任何问题,请留言与我联系

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


php输出xml格式字符串
J2ME Mobile 3D入门教程系列文章之一
XML轻松学习手册
XML入门的常见问题(一)
XML入门的常见问题(三)
XML轻松学习手册(2)XML概念
xml文件介绍及使用
xml编程(一)-xml语法
XML文件结构和基本语法
第2章 包装类
XML入门的常见问题(二)
Java对象的强、软、弱和虚引用
JS解析XML文件和XML字符串详解
java中枚举的详细使用介绍
了解Xml格式
XML入门的常见问题(四)
深入SQLite多线程的使用总结详解
PlayFramework完整实现一个APP(一)
XML和YAML的使用方法
XML轻松学习总节篇