百度提供有查询 ip 归属地的开放接口,当你在搜索框中输入一个 ip 地址进行搜索,就会打开由 ip138 提供的百度框应用,你可以在框内直接输入 ip 地址查询。我查看了页面请求,提取出查询 ip 归属地的接口,据此使用 Qt 写了个简单的 ip 归属地查询应用。可以在电脑和 Android 手机上运行。这里使用了百度 API ,特此声明,仅可作为演示使用,不能用作商业目的。
版权所有 foruok,转载请注明出处( http://blog.csdn.net/foruok )。
这个例子会用到 http 下载、布局管理器、编辑框、按钮、Json 解析等知识,我们会一一解说。图 1 是在手机上输入 IP 地址的效果图:
图1 输入 Ip 地址
再看图 2 ,是点击查询按钮后查询到的结果:
图2 IP 归属地查询结果
好啦,现在我们来说程序。
项目是基于 Qt Widgets Application 模板创建,选择 QWidget 为基类,具体创建过程请参考《Qt on Android:图文详解Hello World全过程》。项目的名字就叫 IpQuery ,创建项目完成后,打开工程文件,为 QT 变量添加网络模块,因为我们查询 IP 时要使用。如下面代码所示:
QT += core gui network
项目模板给我们生成 widget.h / widget.cpp ,修改一下代码,先把界面搞起来。首先看头文件 widget.h :
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
#include
#include
#include "ipQuery.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
protected slots:
void onQueryButton();
void onQueryFinished(bool bOK, QString ip, QString area);
protected:
QLineEdit *m_ipEdit;
QPushButton *m_queryButton;
QLabel *m_areaLabel;
IpQuery m_ipQuery;
};
#endif // WIDGET_H
#include "widget.h"
#include
Widget::Widget(QWidget *parent)
: QWidget(parent), m_ipQuery(this)
{
connect(&m_ipQuery, SIGNAL(finished(bool,QString,QString))
,this, SLOT(onQueryFinished(bool,QString,QString)));
QGridLayout *layout = new QGridLayout(this);
layout->setColumnStretch(1, 1);
QLabel *label = new QLabel("ip:");
layout->addWidget(label, 0, 0);
m_ipEdit = new QLineEdit();
layout->addWidget(m_ipEdit, 0, 1);
m_queryButton = new QPushButton("查询");
connect(m_queryButton, SIGNAL(clicked()),
this, SLOT(onQueryButton()));
layout->addWidget(m_queryButton, 1, 1);
m_areaLabel = new QLabel();
layout->addWidget(m_areaLabel, 2, 0, 1, 2);
layout->setRowStretch(3, 1);
}
Widget::~Widget()
{
}
void Widget::onQueryButton()
{
QString ip = m_ipEdit->text();
if(!ip.isEmpty())
{
m_ipQuery.query(ip);
m_ipEdit->setDisabled(true);
m_queryButton->setDisabled(true);
}
}
void Widget::onQueryFinished(bool bOK, QString ip, QString area)
{
if(bOK)
{
m_areaLabel->setText(area);
}
else
{
m_areaLabel->setText("喔哟,出错了");
}
m_ipEdit->setEnabled(true);
m_queryButton->setEnabled(true);
}
我还在 Widget 构造函数中把 QPushButton 的信号 clicked() 连接到 onQueryButton() 槽上,在槽内调用 IpQuery 类进行 ip 查询。另外还连接了 IpQuery 类的 finished() 信号和 Widget 类的 onQueryFinished() 槽,当查询结束后把结果显示到 m_areaLabel 代表的标签上。
好啦, Widget 解说完毕,咱们接下来看看我实现的 IpQuery 类。我们在项目中添加两个文件 ipQuery.h / ipQuery.cpp 。先看 ipQuery.h :
#ifndef IPQUERY_H
#define IPQUERY_H
#include
#include
#include
class IpQuery : public QObject
{
Q_OBJECT
public:
IpQuery(QObject *parent = 0);
~IpQuery();
void query(const QString &ip);
void query(quint32 ip);
signals:
void finished(bool bOK, QString ip, QString area);
protected slots:
void onReplyFinished(QNetworkReply *reply);
private:
QNetworkAccessManager m_nam;
QString m_emptyString;
};
#endif
在 IpQuery 类体中声明了两个 query() 函数,分别接受 QString 和 uint32 两种格式的 ip 地址。还声明了一个 finished() 信号,有指示成功与否的布尔参数 bOK 、输入的 ip 地址 ip 、返回的归属地 area 。最后定义了一个槽 onReplyFinished() ,响应 QNetworkAccessManager 类的 finished() 信号。
再看 IpQuery.cpp :
#include "ipQuery.h"
#include
#include
#include
#include
#include
#include
#include
#include
IpQuery::IpQuery(QObject *parent)
: QObject(parent)
, m_nam(this)
{
connect(&m_nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(onReplyFinished(QNetworkReply*)));
}
IpQuery::~IpQuery()
{
}
void IpQuery::query(const QString &ip)
{
QString strUrl = QString("http://opendata.baidu.com/api.php?query=%1&resource_id=6006&ie=utf8&format=json").arg(ip);
QUrl url(strUrl);
QNetworkRequest req(url);
req.setRawHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
req.setHeader(QNetworkRequest::UserAgentHeader, "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36");
QNetworkReply *reply = m_nam.get(req);
reply->setProperty("string_ip", ip);
}
void IpQuery::query(quint32 ip)
{
QHostAddress addr(ip);
query(addr.toString());
}
void IpQuery::onReplyFinished(QNetworkReply *reply)
{
reply->deleteLater();
QString strIp = reply->property("string_ip").toString();
if(reply->error() != QNetworkReply::NoError)
{
qDebug() << "IpQuery, error - " << reply->errorString();
emit finished(false, strIp, m_emptyString);
return;
}
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
//qDebug() << "IpQuery, status - " << status ;
if(status != 200)
{
emit finished(false, strIp, m_emptyString);
return;
}
QByteArray data = reply->readAll();
QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString();
//qDebug() << "contentType - " << contentType;
int charsetIndex = contentType.indexOf("charset=");
if(charsetIndex > 0)
{
charsetIndex += 8;
QString charset = contentType.mid(charsetIndex).trimmed().toLower();
if(charset.startsWith("gbk") || charset.startsWith("gb2312"))
{
QTextCodec *codec = QTextCodec::codecForName("GBK");
if(codec)
{
data = codec->toUnicode(data).toUtf8();
}
}
}
int parenthesisLeft = data.indexOf('(');
int parenthesisRight = data.lastIndexOf(')');
if(parenthesisLeft >=0 && parenthesisRight >=0)
{
parenthesisLeft++;
data = data.mid(parenthesisLeft, parenthesisRight - parenthesisLeft);
}
QJsonParseError err;
QJsonDocument json = QJsonDocument::fromJson(data, &err);
if(err.error != QJsonParseError::NoError)
{
qDebug() << "IpQuery, json error - " << err.errorString();
emit finished(false, strIp, m_emptyString);
return;
}
QJsonObject obj = json.object();
QJsonObject::const_iterator it = obj.find("data");
if(it != obj.constEnd())
{
QJsonArray dataArray = it.value().toArray();
QJsonObject info = dataArray.first().toObject();
QString area = info.find("location").value().toString();
emit finished(true, strIp, area);
}
}
我在 IpQuery 构造函数中连接 QNetworkAccessManager 的 finished() 信号和 IpQuery 的 onReplyFinished() 槽。
关键的函数有两个 query(const QString&) 和 onReplyFinished(QNetworkReply*) 。先看 query() 函数,它演示了使用 QNetworkAccessManager 进行 http 下载的基本步骤:
至于 http header 的设置,QNetworkRequest 提供了 setHeader() 方法用来设置常见的如 User-Agent / Content-Type 等 header ,Qt 没有定义的常见 header ,则需要调用 setRawHeader() 方法来设置,具体请参考 http 协议,这里不再细说。
好了,现在来看 onReplyFinished() 函数都干了什么勾当:
根据上面的解说对照代码,应该一切都很容易理解了。这里解释下 5 、 6 两个稍微复杂的步骤。
将 GBK 编码的文本数据转换为 utf-8 ,遵循下列步骤:
更详细的说明请参看 QTextCodec 类的 API 文档。
最后我们说下 Json 数据解析。从 Qt 5.0 开始,引入了对 Json 的支持,之前你可能需要使用开源的 cJson 或者 QJson ,现在好了,官方支持,用起来更踏实了。
Qt 可以解析文本形式的 Json 数据,使用 QJsonDocument::fromJson() 方法即可;也可以解析编译为二进制的 Json 数据,使用 fromBinaryData() 或 fromRawData() 。对应的,toJson() 和 toBinaryData() 则可以反向转换。
我们的示例调用 fromJson() 方法来解析文本形式的 Json 数据。 fromJson() 接受 UTF-8 格式的 Json 数据,还接受一个 QJsonParseError 对象指针,用于输出可能遇到的错误。一旦 fromJson() 方法返回,Json 数据就都被解析为 Qt 定义的各种 Json 对象,如 QJsonObject / QJsonArray / QJsonValue 等等,可以使用这些类的方法来查询你感兴趣的数据。
做个简单的科普,解释下 Json 格式。
JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation),是轻量级的文本数据交换格式,具有自我描述性,容易理解。虽然脱胎于 JavaScript 语言,但它是独立于语言和平台的。
Json 中有两种数据结构:
Json 对象在花括号中表示,最简单的示例:
{"ip":"36.57.177.187"}
如你所见,一对花括号表示一个对象,key 和 value 之间用冒号分割,而 key - value 对之间使用逗号分割。一个对象可以有有多个 key-value 对。下面是两个的示例:
{
"status":"0",
"t":"1401346439107"
}
Json 数组在方括号中表示,最简单的示例,只有基本类型:
["baz", null, 1.0, 2]
看个复杂点的示例:
[
"name":"zhangsan",
{
"age":30,
"phone":"13588888888",
"other": ["xian", null, 1.0, 28]
}
]
在 Qt 中,QJsonValue 代表了 Json 中的值,它有 isDouble() / isBool() / isNull() / isArray() / isObject() / isString() 六个方法来判定值的类型,然后有 toDouble() / toBool() / toInt() / toArray() / toObject() / toString() 等方法用来把 QJsonValue 转换为特定类型的值。
QJsonObject 类代表对象,它的 find() 方法可以根据 key 找 value ,而 keys() 可以返回所有 key 的列表;它还重载了 "[]" 操作符,接受字符串格式的 key 作为下标,让我们像使用数组一样使用 QJsonObject 。
QJsonArray 类代表数组,它有 size() / at() / first() / last() 等等方法,当然了,它也重载了 "[]" 操作符(接受整形数组下标)。
OK,到此为止,基础知识介绍完毕,来看我们 ip 查询时要处理的 Json 数据吧:
{
"status":"0",
"t":"1401346439107",
"data":[
{
"location":"安徽省宿州市 电信",
"titlecont":"IP地址查询",
"origip":"36.57.177.187",
"origipquery":"36.57.177.187",
"showlamp":"1",
"showLikeShare":1,
"shareImage":1,
"ExtendedLocation":"",
"OriginQuery":"36.57.177.187",
"tplt":"ip",
"resourceid":"6006",
"fetchkey":"36.57.177.187",
"appinfo":"", "role_id":0, "disp_type":0
}
]
}
QJsonParseError err;
QJsonDocument json = QJsonDocument::fromJson(data, &err);
这两行根据数据生成 QJsonDocument 对象。然后我们来找根对象名为 "data" 的 key 对应的值,看代码:
QJsonObject obj = json.object();
QJsonObject::const_iterator it = obj.find("data");
找到 data 对应的值,使用 toArray() 转换为 QJsonArray ,使用 QJsonArray 的 first() 方法取第一个元素,使用 toObject() 转为 QJsonObject, 再使用 find() 方法,以 "location" 为 key 查找,把结果转为 String 。说来话长,代码更明白:
QJsonArray dataArray = it.value().toArray();
QJsonObject info = dataArray.first().toObject();
QString area = info.find("location").value().toString();
emit finished(true, strIp, area);
好啦,这个示例全部解说完毕。最后看下电脑上的运行效果,图 3 :
图 3 电脑运行效果图
版权所有 foruok,转载请注明出处( http://blog.csdn.net/foruok )。
我的 Qt on Android 系列文章: