点击上方蓝字关注我们
12.3 搭建UDP服务器与客户端 上一小节介绍的TCP协议是一种面向连接、点对点的通讯协议。而UDP协议是采用无连接,发送数据报的方式进行通信,属于不可靠的通讯方式;也就是数据能不能到达接受端和数据到达的顺序都是不能保证的。由于UDP不用保证数据的可靠性,所以数据的传送速度比TCP协议快。当应用程序需要使用广播方式(群发)通讯时,使用UDP协议就比较方便。比如:一般的视频监控或者视频传输之类的场合就使用UDP协议。简单的说:UDP速度快,TCP更可靠,选择哪一个通讯协议需要看实际的场合需求。
12.3.1 QUdpSocket类介绍 QT的QUdpSocket类提供了UDP的套接字,用于进行UDP协议编程。QUdpSocket属于QAbstractSocket
的子类,关于QAbstractSocket类的成员函数介绍可以参考12.1节。QUdpSocket继承关系如下图所示:
图12-3-1 QUdpSocket继承关系图
下面将介绍QUdpSocket类相关的成员函数。
1. 接收数据报
qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *address = Q_NULLPTR, quint16 *port = Q_NULLPTR)
|
readDatagram函数用于接受不超过maxSize大小的数据包,并将收到数据存储在data中。返回值代表读到的数据包大小;如果返回负数表示接收失败。可以关联void QIODevice::readyRead()信号进行读取数据。后两个参数用于接收数据发送方的IP地址和端口号。
示例:
QHostAddress host;
quint16 port;
udpSocket->readDatagram(datagram.data(),RxLen,&host,&port);
qDebug()<<"对方IP地址:"<<host.toString();
qDebug()<<"对方端口号:"<<port;
qDebug()<<"收到UDP数据:"<<datagram.data();
|
2. 发送数据报
qint64 QUdpSocket::writeDatagram(const char *data, qint64 size, const QHostAddress &address, quint16 port)
qint64 QUdpSocket::writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port)
|
writeDatagram函数用于向指定主机地址和端口发送qint64 size大小的数据报。返回值记录了发送成功数据报字节数。
3. 返回数据报的大小
qint64 QUdpSocket::pendingDatagramSize() const
|
返回收到的数据包大小,如果没有可读的数据报将返回-1。
4. 判断是否有数据可读
bool QUdpSocket::hasPendingDatagrams() const
|
如果有数据可读,发回true。
5. 绑定IP地址和端口
bool QAbstractSocket::bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform)
bool QAbstractSocket::bind(quint16 port = 0, BindMode mode = DefaultForPlatform)
|
bind函数在UDP通信中用于绑定本机的IP地址和端口,方便其他主机连接。
6. 组播设置
bool QUdpSocket::joinMulticastGroup(const QHostAddress &groupAddress) //加入组播
bool QUdpSocket::leaveMulticastGroup(const QHostAddress &groupAddress) //离开组播
|
12.3.2 UDP通信简单介绍 UDP通信中分为三种通信分别为单播、组播和广播
1、单播
(1) 创建套接字
QUdpSocket mSocket;
mSocket = new QUdpSocket();
|
(2) 发送数据到指定的地址和端口号
mSocket->writeDatagram(ui->textEdit->toPlainText().toUtf8(),QHostAddress("192.168.137.1"),6677);
参数:ui->textEdit->toPlainText().toUtf8 要发送的消息
QHostAddress("192.168.137.1") 接收端的ip地址
6677 端口号,要和接收端的一致
|
2、组播,组播和单播的步骤是一样的,只有ip地址处有区别
组播ip地址范围:224.0.0.0-239.255.255.255
例子:mSocket->writeDatagram(ui->textEdit->toPlainText().toUtf8(),QHostAddress("224.0.0.100"),6677);
|
3、广播,广播也只有ip地址和单播有区别
广播地址ip:QHostAddress::Broadcast
例子:
mSocket->writeDatagram(ui->textEdit->toPlainText().toUtf8(),QHostAddress::Broadcast,6677);
|
接收端不管是单播、或者组播还是广播代码都是一样的:
1、创建套接字
QUdpSocket mSocket;
mSocket = new QUdpSocket();
|
2、绑定地址和端口号
mSocket->bind(QHostAddress::AnyIPv4,6677);
参数:AnyIPv4 IPv4
6677 端口号,要和发送端的一致
|
3、等待数据的到来,利用readyRread()
connect(mSocket,SIGNAL(readyRead()),this,SLOT(read_data()));
|
4、读数据
readDatagram(char * data, qint64 maxSize, QHostAddress * address = 0, quint16 * port = 0)
参数:
data:数据
maxSize:数据的大小
address:QHostAddress类型的地址
port:端口号
|
例子:
void UdpRecv::read_data()
{
QByteArray array;
QHostAddress address;
quint16 port;
array.resize(mSocket->bytesAvailable());//根据可读数据来设置空间大小
mSocket->readDatagram(array.data(),array.size(),&address,&port); //读取数据
ui->listWidget->addItem(array);//显示数据
//发送反馈数据
}
|
如果是组播的话涉及到加入组播和退出组播:
加入到组播组 joinMulticastGroup
例子:mSocket->joinMulticastGroup(QHostAddress("224.0.0.100"));
退出组播组 leaveMulticastGroup
例子:mSocket->leaveMulticastGroup(QHostAddress("224.0.0.100"));
|
12.3.3 UDP服务器与客户端创建步骤 UDP协议没有明确的客户端和服务器,所有的端点都是平等的,这需要站在不同的角度看待问题。简单的说,当用户发送信息给别人时就是客户端,当接收别人的信息是就可以看做是服务器端。
创建的步骤如图12-3-2所示。UDP客户端不需要绑定端口,可以直接使用广播方式给指定端口发送数据,UDP服务器端需要绑定端口和IP,为了能接收别人发过来的数据。
图12-3-2 UDP服务器与客户端创建步骤
void Widget::NewUdpServer()
{
/*1. 创建UDP套接字*/
udpSocket=new QUdpSocket(this);
/*2. 绑定全部IP地址和端口号*/
udpSocket->bind(QHostAddress::Any,8888);
/*3. 关联读信号*/
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(UdpClientReadDtatSlot()));
}
void Widget::UdpClientReadDtatSlot()
{
qint64 RxLen=0;
/*4. 判断有没有收到数据*/
if(udpSocket->hasPendingDatagrams())
{
/*5. 获取数据报的大小*/
RxLen=udpSocket->pendingDatagramSize();
QByteArray datagram;
datagram.resize(RxLen);
/*6. 读取数据报*/
QHostAddress host;
quint16 port;
udpSocket->readDatagram(datagram.data(),RxLen,&host,&port);
QString msg=datagram.data();
/*7. 打印收到的数据*/
qDebug()<<"对方IP地址:"<<host.toString();
qDebug()<<"对方端口号:"<<port;
qDebug()<<"收到数据长度:"<<RxLen;
qDebug()<<"收到数据内容:"<<msg;
}
}
|
void Widget::NewUdpClient()
{
/*1. 创建UDP套接字*/
udpSocket=new QUdpSocket(this);
/*2. 创建定时器,定时广播*/
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1000);
}
void Widget::update()
{
QString msg="欢迎学习UDP网络编程!";
int length=0;
QByteArray array=msg.toLocal8Bit();
/*3. 进行广播方式发送数据*/
length=udpSocket->writeDatagram(array,QHostAddress("192.168.18.237"),8888);
if(length<=0)
{
qDebug()<<"数据发送错误!\n";
}
}
|
以上代码通过定时器,每一秒钟向192.168.18.237: 8888地址发送数据。地址也可以填写为QHostAddress::Broadcast 表示广播地址。
12.3.4 UDP网络调试助手实例 本小节编写UDP网络调试助手实例可以用于测试UDP协议数据收发,支持UDP数据的发送和接收,同时也支持广播地址发送。调试助手界面如下:(配套代码CH12-4)
图12-3-3 UDP调试助手运行效果
1. “widget.ui”文件示例
(2) “widget.h”文件代码示例
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QWidget>
#include <QHostInfo>
#include <QUdpSocket>
#include <QtNetwork>
#include <QHostInfo>
#include <QDebug>
#include <QHostAddress>
#include <QMessageBox>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QComboBox>
#include <QFile>
#include <QTimer>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
int TxCount;
int RxCount;
QUdpSocket *udpSocket;
QHostAddress OppositeHost;//存放连接远程IP
quint16 OppositePort; //远程端口
void NewUdpServer();
void NewUdpClient();
void comboBox_config();
private slots:
void UdpClientReadDtatSlot();
void on_comboBox_protocol_activated(int index);
void on_pushButton_connect_clicked();
void on_pushButton_SendData_clicked();
void on_pushButton_clear_clicked();
void on_About_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
|
(3) “widget.cpp”文件代码示例
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{
ui->setupUi(this);
comboBox_config();
}
Widget::~Widget()
{
delete ui;
}
//基本配置
void Widget::comboBox_config()
{
//1.1 配置协议
ui->comboBox_protocol->addItem("Udp Server");
ui->comboBox_protocol->addItem("Udp Client");
//1.2. 获取并配置本地IP地址
QList<QHostAddress> list = QNetworkInterface::allAddresses();
for(int i=0;i<list.count();i++)
{
QHostAddress addr=list.at(i);
if(addr.protocol() == QAbstractSocket::IPv4Protocol)
{
ui->comboBox_ipaddress->addItem(addr.toString());
}
}
ui->comboBox_ipaddress->addItem("255.255.255.255");
ui->comboBox_ipaddress->addItem("0.0.0.0");
//1.3 设置comboBox支持编辑
ui->comboBox_ipaddress->setEditable(true);
//1.4. 设置默认端口号
ui->lineEdit_prot->setText("8080");
//1.5 标题设置
this->setWindowTitle("Udp调试助手");
TxCount=0;
RxCount=0;
}
void Widget::NewUdpClient()
{
/*1. 创建UDP套接字*/
udpSocket=new QUdpSocket(this);
}
void Widget::NewUdpServer()
{
/*1. 创建UDP套接字*/
udpSocket=new QUdpSocket(this);
/*2. 绑定IP地址和端口号*/
QHostAddress address(ui->comboBox_ipaddress->currentText());
qint16 prot=ui->lineEdit_prot->text().toInt();
udpSocket->bind(address,prot);
/*3. 关联读信号*/
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(UdpClientReadDtatSlot()));
}
void Widget::UdpClientReadDtatSlot()
{
qint64 RxLen=0;
/*4. 判断有没有收到数据*/
if(udpSocket->hasPendingDatagrams())
{
/*5. 获取数据报的大小*/
RxLen=udpSocket->pendingDatagramSize();
QByteArray datagram;
datagram.resize(RxLen);
/*6. 读取数据报*/
RxCount+=udpSocket->readDatagram(datagram.data(),RxLen,&OppositeHost,&OppositePort);
QString msg=datagram.data();
msg=msg.toLocal8Bit();
/*7. 显示收到的数据*/
QString text;
text="【"+OppositeHost.toString()+":"+QString::number(OppositePort)+"】:"+msg;
ui->plainTextEdit_ShowData->appendPlainText(text);
/*8. 显示接收的数量*/
ui->lcdNumber_RxNumber->display(RxCount);
}
}
//协议选择
void Widget::on_comboBox_protocol_activated(int index)
{
switch(index){
case 0://服务器
ui->label_ip->setText("(3)本地IP地址");
ui->label_prot->setText("(2)本地端口号");
break;
case 1://客户端
ui->label_ip->setText("(3)远程IP地址");
ui->label_prot->setText("(2)远程端口号");
break;
default:
break;
}
}
void Widget::on_pushButton_connect_clicked()
{
if(ui->pushButton_connect->text()=="连接")
{
ui->pushButton_connect->setText("断开连接");
ui->comboBox_ipaddress->setEnabled(false);
ui->comboBox_protocol->setEnabled(false);
ui->lineEdit_prot->setEnabled(false);
switch(ui->comboBox_protocol->currentIndex())
{
case 0: //服务器模式
NewUdpServer();
ui->pushButton_SendData->setEnabled(false);
break;
case 1: //客户端模式
NewUdpClient();
ui->pushButton_SendData->setEnabled(true);
break;
}
}else
{
ui->pushButton_connect->setText("连接");
ui->comboBox_ipaddress->setEnabled(true);
ui->comboBox_protocol->setEnabled(true);
ui->lineEdit_prot->setEnabled(true);
udpSocket->close();
}
}
//发送数据
void Widget::on_pushButton_SendData_clicked()
{
QString msg=ui->lineEdit_InputTxDtat->text();
int length=0;
QByteArray array=msg.toLocal8Bit();
switch(ui->comboBox_protocol->currentIndex())
{
case 0: //服务器模式
break;
case 1: //客户端模式
QHostAddress address(ui->comboBox_ipaddress->currentText());
qint16 prot=ui->lineEdit_prot->text().toInt();
length=udpSocket->writeDatagram(array,address,prot);
if(length<=0)
{
QMessageBox::warning(this,"发送错误","数据发送失败!",QMessageBox::Ok);
return;
}
//显示发送的数量
TxCount+=length;
ui->lcdNumber_TxNumber->display(TxCount);
break;
}
}
void Widget::on_pushButton_clear_clicked()
{
TxCount=0;
RxCount=0;
ui->lcdNumber_RxNumber->display(0);
ui->lcdNumber_TxNumber->display(0);
}
void Widget::on_About_clicked()
{
QMessageBox::aboutQt(this);
}
|
12.3.5 子线程创建UdpSocket实例 实际开发中进行网络编程都会用到多线程,一般将数据接收代码放在子线程,主线程实现更新界面。下面编写的示例,是将UDP套接字相关代码放在子线程中,在子线程中完成了数据的接收;收到数据之后,通过信号传递给主线程,再更新显示到UI界面上。注意:本实例没有重写QThread类,是通过QObject::moveToThread函数将对象移动到新的线程中执行,这样可以解决主线程和子线程之间跨线程发送信号导致的一些错误问题。(配套代码CH12-8)
运行效果如下:
1. “widget.ui”文件示例
2. “udpdata.h”文件示例
#ifndef UDPTHREAD_H
#define UDPTHREAD_H
#include <QThread>
#include <QHostAddress>
#include <QHostInfo>
#include <QUdpSocket>
#include <QtNetwork>
#include <QHostInfo>
#include <QImage>
#include <QPixmap>
#include <QDebug>
class UdpData : public QObject
{
Q_OBJECT
public:
UdpData(quint16 port, QString addr="0.0.0.0");
QUdpSocket *udpSocket;
QHostAddress OppositeHost;//存放连接远程IP
quint16 OppositePort; //远程端口
signals:
void TxUdpData(QString data); //信号声明
public slots:
void UdpClientReadDtatSlot();
};
#endif // UDPTHREAD_H
|
3. “udpdata.cpp”代码示例
#include "udpdata.h"
UdpData::UdpData(quint16 port, QString addr)
{
/*1. 创建 UDP 套接字*/
udpSocket=new QUdpSocket(this);
/*2. 绑定全部 IP 地址和端口号*/
if(!udpSocket->bind(QHostAddress(addr),port))
{
qDebug()<<"绑定错误";
}
/*3. 关联读信号*/
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(UdpClientReadDtatSlot()));
}
void UdpData::UdpClientReadDtatSlot()
{
qint64 RxLen=0;
/*4. 判断有没有收到数据*/
while(udpSocket->hasPendingDatagrams())
{
/*5. 获取数据报的大小*/
RxLen=udpSocket->pendingDatagramSize();
QByteArray datagram;
datagram.resize(RxLen);
/*6. 读取数据报*/
udpSocket->readDatagram(datagram.data(),RxLen,&OppositeHost,&OppositePort);
QString msg=datagram.data();
msg=msg.toLocal8Bit();
/*7. 显示收到的数据*/
QString text;
text="【"+OppositeHost.toString()+":"+QString::number(OppositePort)+"】:"+msg;
/*8. 发送信号给主线程*/
emit TxUdpData(text);
}
}
|
4. “widget.h”文件示例
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "udpdata.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
UdpData *udpDataTx;
QThread *thread;
private slots:
void on_pushButton_NewUdp_clicked();
void update(QString data);
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
|
5. “widget.cpp”文件示例
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{
ui->setupUi(this);
//1. 获取并配置本地IP地址
QList<QHostAddress> list = QNetworkInterface::allAddresses();
for(int i=0;i<list.count();i++)
{
QHostAddress addr=list.at(i);
if(addr.protocol() == QAbstractSocket::IPv4Protocol)
{
ui->comboBox_ipaddress->addItem(addr.toString());
}
}
//2.设置UI控件默认属性
ui->comboBox_ipaddress->setEditable(true);
ui->lineEdit_prot->setText("8888");
}
Widget::~Widget()
{
delete ui;
}
//开始接收数据
void Widget::on_pushButton_NewUdp_clicked()
{
quint16 port=ui->lineEdit_prot->text().toInt();
//创建线程
thread=new QThread;
//实例化对象
udpDataTx =new UdpData(port,ui->comboBox_ipaddress->currentText());
//将udpDataTx移动到新的线程中执行
udpDataTx->moveToThread(thread);
//关联线程发送的数据信号
connect(udpDataTx, SIGNAL(TxUdpData(QString)), this, SLOT(update(QString)));
thread->start();
ui->pushButton_NewUdp->setEnabled(false);
}
void Widget::update(QString data)
{
ui->plainTextEdit->appendPlainText(data);
}
|
12.3.6 UDP组播穿透路由器 局域网内的两台机器如果隔有路由器,那么这两台机器之间不能进行广播通信,但是我们可以换成组播的通信的方式,达到相互通信的效果。
QUdpSocket udp_socket;
udp_socket.bind(QHostAddress::Any, udp_listen_port, QUdpSocket::ReuseAddressHint);
QHostAddress mcast_addr("224.0.0.17");
udp_socket.setSocketOption(QAbstractSocket::MulticastLoopbackOption, 0);//禁止本机接收
udp_socket.joinMulticastGroup(mcast_addr);//这句是关键,加入组播地址
|
QHostAddress mcast_addr("224.0.0.17");//组播地址与服务器相同
QUdpSocket udp_socket;
udp_socket.writeDatagram(datagram, mcast_addr, UDP_SEND_PORT);//向服务器发送数据(UDP_SEND_PORT与服务器的监听端口相同)
|
12.4 HTTP网络通信 前面介绍的QTcpSocket、QUdpSocket、QTcpServer都是网络传输层的类,属于底层的网络编程接口。如果需要进行Http通信,比如:浏览网页、请求网络数据、下载上传文件等操作时,就需要用到更高层次,更加强大的编程接口。
QT5版本中关于Http通信相关的类有QNetworkReply、QNetworkAccessManager、QNetworkRequest等。其中QNetworkReply类负责对网络请求进行响应;QNetworkRequest类负责向网络上发送请求;QNetworkAccessManager类包含了前两个类,允许应用程序发送网络请求和接收网络应答。
12.4.1 实现网页浏览 本小节利用QNetworkAccessManager类获取网页数据,可以实现类似浏览器的功能。
QNetworkAccessManager类有一个get函数用于请求网络数据,get函数原型如下:
QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request)
|
形参填入一个QNetworkRequest类,返回值是一个QNetworkReply指针。
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
manager->get(QNetworkRequest(QUrl("http://www.baidu.com")));
|
get函数提交请求之后,如果请求的地址进行了回应会触发一个finished信号。这个过程是异步操作,get函数不会进行阻塞。如果想要读取回应的数据,可以将finished信号关联到指定的槽函数。finished信号的原型如下:
[signal] void QNetworkAccessManager::finished(QNetworkReply *reply)
|
finished函数的形参传入的是QNetworkReply类,QNetworkReply类的基类是QIODevice类,包含了IO读写的一系列函数。
void Widget::GetHtmlData()
{
/*1. 实例化QNetworkAccessManager*/
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
/*2. 关联finished信号*/
connect(manager, SIGNAL(finished(QNetworkReply*)),this, SLOT(replyFinished(QNetworkReply*)));
/*3. 获取网页数据*/
manager->get(QNetworkRequest(QUrl("http://www.baidu.com")));
}
//槽函数
void Widget::replyFinished(QNetworkReply*network)
{
//判断错误
if(network->error() == QNetworkReply::NoError)
{
//读取返回的所有数据
QByteArray byte_array=network->readAll();
}
network->deleteLater();//释放network占用的空间
}
|
下面通过get函数和finished信号编写一个显示简易的网页浏览器实例,演示网络数据获取的效果。
创建工程时,需要在pro工程文件中加上QT+= network。添加network模块,不添加会导致网络编程的相关类识别不了。(配套代码CH12-5)
简易的网页浏览器实例运行效果如下:
图12-4-1 显示HTML页面
1. “widget.ui”文件示例
图12-4-2 简易浏览器UI设计界面
界面上加入了一个单行编辑器,用于获取用于输入的网址信息,负责显示网页数据的是一个textEdit控件。
2. “widget.cpp”文件代码示例
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{
ui->setupUi(this);
/*1. 实例化QNetworkAccessManager*/
manager = new QNetworkAccessManager(this);
/*2. 关联finished信号*/
connect(manager, SIGNAL(finished(QNetworkReply*)),this, SLOT(replyFinished(QNetworkReply*)));
}
Widget::~Widget()
{
delete ui;
}
//槽函数
void Widget::replyFinished(QNetworkReply*network)
{
//无错误返回
if(network->error() == QNetworkReply::NoError)
{
//读取所有数据
QByteArray byte_array=network->readAll();
QString str(byte_array);
ui->textEdit->setHtml(str);
}
//删除reply,不能在repyfinished里直接delete,要调用deletelater;
network->deleteLater();
}
void Widget::on_pushButton_Get_clicked()
{
QString url=ui->lineEdit->text();
/*3. 获取网页数据*/
manager->get(QNetworkRequest(QUrl(url)));
}
|
3. “widget.h”代码示例
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
QNetworkAccessManager *manager;
public slots:
void replyFinished(QNetworkReply* network);
private slots:
void on_pushButton_Get_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
|
12.4.2 在线创建二维码 生成二维码功能在目前比较常用,下面将通过QNetworkAccessManager类与其他网站提供的数据接口交互,创建二维码图片。程序用到的函数和编程模型与上一节实现的“简易浏览器”类似。常用的数据接口API网站:http://www.k780.com/
二维码创建实例运行界面如下:(配套代码CH12-7)
图12-4-2-1 二维码生成效果
1. “widget.ui”UI设计界面示例
图12-4-2-2 UI设计界面
2. “widget.cpp”文件代码示例
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{
ui->setupUi(this);
/*设置标题*/
this->setWindowTitle("创建二维码");
/*1. 实例化QNetworkAccessManager*/
manager = new QNetworkAccessManager(this);
/*2. 关联finished信号*/
connect(manager, SIGNAL(finished(QNetworkReply*)),this, SLOT(replyFinished(QNetworkReply*)));
//ui->label_show->setScaledContents(true);
ui->pushButton_save->setEnabled(false);
}
Widget::~Widget()
{
delete ui;
}
//槽函数
void Widget::replyFinished(QNetworkReply*network)
{
//无错误返回
if(network->error() == QNetworkReply::NoError)
{
//读取所有数据
QByteArray byte_array=network->readAll();
QPixmap pixmap;
//加载图片
if(pixmap.loadFromData(byte_array,"png",Qt::AutoColor))
{
ui->pushButton_save->setEnabled(true);
ui->label_show->setPixmap(pixmap);
}
else{
ui->label_show->setText("图片加载失败!\n");
}
}else
{
QMessageBox::warning(this, tr("错误信息提示"),tr("获取二维码图片失败!""检查网络是否正常!"),
QMessageBox::Ok);
}
//删除reply,不能在repyfinished里直接delete,要调用deletelater;
network->deleteLater();
}
/*
请求参数说明:
参数名称 类型 是否必须 说明
datastring 1 需要生成二维码内例如:test
levelstring 0 纠错级别 <'L','M','Q','H'>,默认: L
sizenumber 0 大小 <1-20>,默认: 6
返回参数说明:
参数名称类型说明
success{0/1}执行结果,当出错时将会出现此节点出现,否则不出现
msgstring出错消息,当出错时将会出现此节点,否则不出现
*/
void Widget::on_pushButton_Get_clicked()
{
/*接收输入框的数据*/
QString str=ui->lineEdit->text();
if(str.isEmpty())
{
QMessageBox::warning(this, tr("错误信息提示"),tr("生成的数据为空!"),QMessageBox::Ok);
return;
}
/*保存接口地址*/
QString text;
text="http://api.k780.com:88/?app=qr.get&data=";
text+=str;
text+="&level=L&size=10";
/*3. 获取网页数据*/
manager->get(QNetworkRequest(QUrl(text)));
}
void Widget::on_pushButton_save_clicked()
{
/*获取保存的文件名称*/
QString fileName = QFileDialog::getSaveFileName(this, tr("选择保存的文件名称"), "new_file.png",
tr("Images (*.png *.xpm *.jpg)"));
/*获取标签上的图片*/
QPixmap pix=*(ui->label_show->pixmap());
if(pix.save(fileName)!=true)
{
QMessageBox::warning(this, tr("错误信息提示"),tr("保存二维码图片失败!""检查网络是否正常!"),
QMessageBox::Ok);
}
}
|
3. “widget.h”文件示例
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QMessageBox>
#include <QFileDialog>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
QNetworkAccessManager *manager;
public slots:
void replyFinished(QNetworkReply* network);
private slots:
void on_pushButton_Get_clicked();
void on_pushButton_save_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
|
12.4.3 实现网络文件下载 本小节通过QNetworkReply类实现浏览器的下载功能,可以下载互联网上的文件。因为QNetworkReply继承于QIODevice类,所以当网络上有数据可读时,会触发readyRead()信号,可以在readyRead()信号关联的槽函数里读取数据。要完成网络文件下载功能,还需要用到QNetworkReply类的downloadProgress()和finished()信号。
这两个信号的原型如下:
[signal] void QNetworkReply:: downloadProgress (qint64 bytesReceived, qint64 bytesTotal)
[signal] void QNetworkReply::finished()
|
其中downloadProgress信号是在下载进度更新时触发,可以用来更新下载的进度条,形参bytesTotal代表文件的总字节数,bytesReceived代表当前已经下载的字节数。finished()信号用于报告网络请求已经处理完毕,也就是说当文件下载成功时,就会触发finished()信号。
下载文件运行效果如下:(配套代码CH12-6)
图12-4-3 网络文件下载运行效果
下载的是一个Linux下QT的安装包。如果需要向HTTP服务器上传数据可以使用:QNetworkAccessManager 类的post()和put()两个函数。
1. “widget.ui”文件设计界面示例
图12-4-4 下载器UI设计界面
2. “widget.cpp”文件代码示例
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{
ui->setupUi(this);
/*1. 实例化QNetworkAccessManager*/
Manager = new QNetworkAccessManager(this);
ui->progressBar->setValue(0);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_Get_clicked()
{
if(ui->lineEdit->text().isEmpty())
{
QMessageBox::warning(this,tr("错误提示"),tr("下载地址为空!"),QMessageBox::Ok);
return;
}
QUrl url=ui->lineEdit->text();
/*3. 创建文件*/
QFileInfo FileInfo(url.path());
QString fileName = QFileDialog::getSaveFileName(this, tr("创建文件"),FileInfo.fileName());
NewFile =new QFile(fileName);
if(!NewFile->open(QIODevice::WriteOnly))
{
QString str=fileName+"创建失败";
QMessageBox::warning(this,tr("错误提示"),str,QMessageBox::Ok);
return;
}
/*4. 获取网页数据*/
Reply=Manager->get(QNetworkRequest(QUrl(url)));
/*5. 关联downloadProgress和finished信号*/
connect(Reply,
SIGNAL(downloadProgress(qint64,qint64)),this, SLOT(ReplydownloadProgress(qint64,qint64)));
connect(Reply, SIGNAL(finished()),this, SLOT(ReplyFinished()));
connect(Reply, SIGNAL(readyRead()),this, SLOT(ReplyReadyRead()));
}
//下载请求完成
void Widget::ReplyFinished()
{
NewFile->flush();//刷新缓冲区
NewFile->close();//关闭文件
delete NewFile; //释放NewFile对象。
Reply->deleteLater();//释放Reply
}
//更新下载进度
void Widget::ReplydownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
ui->progressBar->setMaximum(bytesTotal);
ui->progressBar->setValue(bytesReceived);
}
void Widget::ReplyReadyRead()
{
//读取网络数据,写入文件
if(NewFile)
{
NewFile->write(Reply->readAll());
}
}
|
3. “widget.h”文件代码示例
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QFile>
#include <QFileInfo>
#include <QFileDialog>
#include <QMessageBox>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
QNetworkAccessManager *Manager;
QNetworkReply *Reply;
QFile *NewFile;
public slots:
void ReplyFinished();
void ReplydownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void ReplyReadyRead();
private slots:
void on_pushButton_Get_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
|
技术合作与咨询
QQ:1126626497 关注我长按二维码可识别微信号:xl1126626497
---------------------------------------------------------------------------------------------------------------------- 我们尊重原创,也注重分享,文章来源于微信公众号:DS小龙哥 嵌入式技术资讯,建议关注公众号查看原文。如若侵权请联系qter@qter.org。 ----------------------------------------------------------------------------------------------------------------------
|