QT网络编程

一、概述

1、什么是计算机网络?

计算机网络,是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统、网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。

2、什么是网络协议?

网络协议是一种特殊的软件,是计算机网络实现其功能的最基本的机制。网络协议的本质就是规则,即各种硬件和软件必须遵循的共同守则。网络协议并不是一套单独的软件,它融合于其它所有的软件甚至硬件系统中,因此可以说协议在网络中无所不在

3、什么是协议栈?

为了减少网络设计的复杂性,绝大多数网络采用分层设计的方法。所谓分层设计,就是按照信息的流动过程将网络的整体功能分解为一个个的功能层,不同机器上的同等功能层之间采用相同的协议,同一机器上的相邻功能层之间通过接口进行信息传递。各层的协议和接口统称为协议栈

4、OSI(Open System Interconnection)的七层协议

描述计算机网络各协议层的一般方法是采用国际标准化组织(International Standardization Organization, ISO)的计算机通信开放系统互连(Open System Interconnection, OSI)模型,简称ISO/OSI网络协议模型
– 应用层,为应用程序提供服务,处理业务逻辑,如http、ftp、pop3、smtp
– 表示层,处理在两个通信系统中交换信息的方式,如ASCII、JPEG、MJPG
– 会话层,建立、管理、终止会话,如SMB – 传输层,向用户提供可靠的端到端服务,如 TCP/UDP
– 网络层,为网络不同的主机提供通信,从中选择最适当的路径,如:IP协议
– 数据链路层,实现物理寻址,建立通信链路,如MAC
– 物理层,为数据端设备提供传送的物理媒体,对网卡的硬件驱动

5、TCP/IP协议族

TCP/IP不是个单一的网络协议,而是由一组具有层次关系的网络协议组成的协议家族,简称TCP/IP协议族
– TCP:传输控制协议,面向连接,可靠的全双工的字节流
– UDP:用户数据报协议,无连接,不如TCP可靠但速度快
– ICMP:网际控制消息协议,处理路由器和主机间的错误和控制消息
– IGMP:网际组管理协议,用于多播
– IPv4:网际协议版本4,使用32位地址,为TCP、UDP、ICMP和IGMP提供递送分组服务
– IPv6:网际协议版本6,使用128位地址,为TCP、UDP和ICMPv6提供递送分组服务
 

6、IP地址

IP地址是计算机在互联网中的唯一标识,具体可分为IPv4协议地址和IPv6协 议地址:
– IPv4:32位整数,如“0xC0A80F64”
– IPv6:128位整数,如“fe80:0000:0000:0000:dad9:b217:f207:bc35” 
Qt中使用QHostAddress对象来表示一个IP地址,包括IPv4和IPv6,可以通过toString成员函数,将其转换为点分字符串的形式,也就是平时看到的地址,例如IPv4地址“0xC0A80F64”,对应字符串为“192.168.15.100”
QHostAddress的构造函数
– QHostAddress(quint32 ip4Addr)
– QHostAddress(const QString & address) 
– ......
查询主机IP地址命令
– linux中使用“ifconfig”
– windows中“ipconfig”
特殊的IP地址
– QHostAddress::LocalHost 注:对应“127.0.0.1”表示本地环回地址,常用于本地测试
– QHostAddress::Any           注:对应“0.0.0.0”表示任意IP地址,常用于服务器
– QHostAddress::Broadcast 注:对应“255.255.255.255”表示广播地址,用于局域网广播消息
通过IP地址判断两台主机是否可以通信
– ping 对方IP地址/域名 注:用于确定本地主机是否能与对方主机成功交换(发送与接收)数据包,再根据 返回的信息,就可以推断通信响应速度、网络是否通畅

QT的网络编程

QT提供了QtNetWork网络模块进行网络编程。
其中包含一些低级别的类,例如:QTcpSocket、QTcpServer 和 QUdpSocket,表示低级的网络概念;一些高级别的类,例如:QNetworkRequest、QNetworkReply 和 QNetworkAccessManager,使用常见的协议执行网络操作;它还提供其他的类,例如:QNetworkConfiguration、QNetworkConfigurationManager 和QNetworkSession 用于承载管理。
使用QT编写网络编程需要链接到网络模块,在.pro中添加QT+=network网路模块库。

二、UDP通信

1、UDP协议简介

UDP (User Data Protocol,UDP)即用户数据报协议,是OSI参考模型中一 种无连接的传输层协议,它是一种简单轻量级、不可靠、面向数据报、无连接的 传协议,可以应用在可靠性要求不苛刻的场景 。 UDP发送和接收数据都不需要建立连接,只需要绑定IP地址和端口号就可以了。发送数据直接发送,接收数据需要循环判断数据是否接受完毕。

2、适合使用UDP通信的情况

通信的交互数据大多为短消息
拥有大量的客户端
对数据安全性无特殊要求
网络负担重,但对响应实时性要求较高

3、UDP通信模型

QT网络编程_第1张图片
通过上图可以看出,在UDP方式下,客户端并不与服务器建立连接,他只负责调用发送函数向服务器发送数据报,类似的服务器也不从客户端接收数据,只负责调用接收函数,等待来自某客户端的数据到达。

4、UDP通信类

QT提供了QUdpSocket类,该类封装了UDP套接字,可以非常方便的建立UDP通信, 它是从QAbstractSocket继承而来的, 以数据报传输数据形式, 发送数据报使用函数QUdpSocket::writeDatagram(),数据报的长度一般少于512字节,每个数据报需要包含发送者和接受者的IP地址和端口信息。

要进行UDP数据接收,要用QUdpSocket::bind()函数先绑定一个端口,用于接收传入的数据报。当有数据报传入时会发射readRead()信号,使用readDatagram()函数来读取接收到的数据报。

相关函数原型:

UDP消息传送有单播、广播、组播三种。
单播模式:一个UDP客户端发出的数据报只发送到另一个指定的地址和端口的UDP客户端,是一对一数据传输
广播模式:一个UDP客户端发出的数据报,在同一网络范围内其他所有的UDP客户端都可以接收到。QUdpSocket支持IPv4广播。广播经常用于实现网络发现的协议。要获取广播数据只需要将数据报发送到特定的接收端地址QHostAddress::Broadcast(255.255.255.255)
组播模式:也称为多播。UDP客户端加入到另一个组播IP地址指定的多播组(QUdpSocket::joinMuliticastGroup()),成员想组播地址发送的数据报组内成员都可以接收到,类似于QQ群的功能。一般即时通信都是基于UDP通信的。

服务器端:UdpServer

界面放一个QTextBroWse控件来显示客户端发送来的所有消息,并改对象名为text;

在.pro文件添加QT+=network

UdpServer.h内容

#ifndef UDPSERVER_H
#define UDPSERVER_H

#include
#include
namespace Ui {
class UdpServer;
}

class UdpServer : public QDialog
{
    Q_OBJECT

public:
    explicit UdpServer(QWidget *parent = 0);
    ~UdpServer();
private slots:
    void readPendingDatagrams();

private:
    Ui::UdpServer *ui;
    QUdpSocket *udpSocket;
};

#endif // UDPSERVER_H
UdpServer.cpp

#include "udpserver.h"
#include "ui_udpserver.h"

UdpServer::UdpServer(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::UdpServer)
{
    ui->setupUi(this);
    udpSocket = new QUdpSocket(this);
    udpSocket->bind(QHostAddress::LocalHost, 7755);
    connect(udpSocket, SIGNAL(readyRead()),this, SLOT(readPendingDatagrams()));
}

UdpServer::~UdpServer()
{
    delete ui;
}

void UdpServer::readPendingDatagrams()
{
    QHostAddress sender;
    quint16 senderPort;
    while (udpSocket->hasPendingDatagrams())
    {
        QByteArray datagram;
        datagram.resize(udpSocket->pendingDatagramSize());
        udpSocket->readDatagram(datagram.data(), datagram.size(),&sender, &senderPort);
        QString strMes(datagram);
        ui->text->insertPlainText(strMes);
    }
    QString text = "hello ...";
    QByteArray datagram = text.toLocal8Bit();
    udpSocket->writeDatagram(datagram.data(),datagram.size(),sender, senderPort);
}

客户端UdpCliear
在.pro中添加QT+=network
界面文件
 
QT网络编程_第2张图片
UdpCliear.h头文件内容

#ifndef UDPCLIER_H
#define UDPCLIER_H

#include
#include
namespace Ui {
class UdpClier;
}

class UdpClier : public QDialog
{
    Q_OBJECT

public:
    explicit UdpClier(QWidget *parent = 0);
    ~UdpClier();
private slots:
    void readPendingDatagrams();
    void on_button_clicked();

private:
    Ui::UdpClier *ui;
    QUdpSocket *udpSocket;
};

#endif // UDPCLIER_H
UdpClear.cpp

#include "udpclier.h"
#include "ui_udpclier.h"

UdpClier::UdpClier(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::UdpClier)
{
    ui->setupUi(this);
    udpSocket = new QUdpSocket(this);
    udpSocket->bind(QHostAddress::LocalHost, udpSocket->localPort());
    connect(udpSocket, SIGNAL(readyRead()),this, SLOT(readPendingDatagrams()));
}

UdpClier::~UdpClier()
{
    delete ui;
}

void UdpClier::on_button_clicked()
{
    QString text = ui->sends->toPlainText();
    QByteArray datagram = text.toLocal8Bit();
    udpSocket->writeDatagram(datagram.data(),datagram.size(),QHostAddress::LocalHost, 7755);
}

void UdpClier::readPendingDatagrams(){
    while (udpSocket->hasPendingDatagrams())
    {
        QByteArray datagram;
        datagram.resize(udpSocket->pendingDatagramSize());
        QHostAddress sender;
        quint16 senderPort;
        udpSocket->readDatagram(datagram.data(), datagram.size(),&sender, &senderPort);
        QString text = QString(datagram);
        ui->text->insertPlainText(text);
        ui->text->insertPlainText("\n");
    }
}

三、TCP通信

1、TCP协议简介

 
TCP(Transmission Control Protocol)传输控制协议是一种可靠、面向连接、面向数据流的传输协议,许多高层应用的协议(HTTP,FTP等)都是以他为基础的,TCP适合数据的连续传输。
TCP和UDP的差别。
 
QT网络编程_第3张图片
 

2、TCP编程模型。

QT网络编程_第4张图片
首先启动服务器,一段时间后启动客户端,它与此服务器警告三次握手后建立连接。此后的一段时间内,客户端向服务器发送一个请求,服务器处理这个请求,并为客户端发回一个响应。这个过程一直持续下去,指导客户端为服务器发送一个文件结束符,并关闭客户端连接,接着服务器也关闭服务器端的连接,结束运行或等待一个新的客户端连接。

4、QT中的TCP通信

QT中通过QTcpSocket类和QTcpServer类实现TCP的编程,QTcpSocket实现客户端,QTcpServer实现服务器。

服务器的实现一般如下:

  1. 调用QTcpServer::listen()启动服务端并监听;
  2. 每当接收到客户端的请求,就会发送QTcpServer::newConnection()信号;
  3. 在槽函数中,调用QTcpServer::nextPendingConnection()接受请求,返回QTcpSocket对象用于和客户端通信(类似于UC的accept函数返回的fp),然后将QTcpSocket对象保存到链表中。
  4. 当有客户端发送来消息时,就会触发readyread()信号
  5. 在槽函数中调用readAll()函数介绍消息,并处理消息。

QTcpSocket类客户端建立TCP连接。一般使用逻辑如下:

  1. 调用QTcpSocket::connectToHost连接到服务器;
  2. 使用write函数进行发送数据;
  3. 当服务器有数据发送来的时候就会触发readyread()信号。
  4. 在槽函数中使用readAll()函数接受服务器发送来的消息,并处理消息。
5、实现基于TCP的网络聊天室,有客户端(cliear)和服务器(server)
      Cliear.ui   - 界面文件
QT网络编程_第5张图片
      Cliear.h    -客户端
#ifndef CLIENTDIALOG_H
#define CLIENTDIALOG_H
#include 
#include 
#include 
#include 
#include 
namespace Ui {
class ClientDialog;
}
class ClientDialog : public QDialog
{
    Q_OBJECT
public:
    explicit ClientDialog(QWidget *parent = 0);
    ~ClientDialog();
private slots:
    //发送按钮对应的槽函数
    void on_sendButton_clicked();
    //连接服务器按钮对应的槽函数
    void on_connectButton_clicked();
    //和服务器连接成功时执行的槽函数
    void onConnected();
    //和服务器断开连接时执行的槽函数
    void onDisconnected();
    //接收聊天消息的槽函数
    void onReadRead();
    //网络异常是执行的槽函数
    void onError();
private:
    Ui::ClientDialog *ui;
    bool status;//标记客户端状态,true:在线状态,false:离线状态
    QTcpSocket tcpSocket;//和服务器通信的TCP的套接字
    QHostAddress serverIp;//服务器的IP
    quint16 serverPort;//服务器端口
    QString username;//聊天室昵称
};
#endif // CLIENTDIALOG_H

Cliear.cpp -  客户端实现

#include "clientdialog.h"
#include "ui_clientdialog.h"

ClientDialog::ClientDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::ClientDialog)
{
    ui->setupUi(this);
    status = false;//初始化离线状态
    //连接服务器服务器成功时,发送信号connected,连接到和服务器连接成功时执行的槽函数
    connect(&tcpSocket,SIGNAL(connected()),this,SLOT(onConnected()));
    //和服务器断开连接,发送信号disconnected,连接和服务器断开连接时执行的槽函数
    connect(&tcpSocket,SIGNAL(disconnected()),this,SLOT(onDisconnected()));
    //收到服务器转发的聊天消息时,发送信号readyRead,连接到接收聊天消息的槽函数
    connect(&tcpSocket,SIGNAL(readyRead()),this,SLOT(onReadRead()));
    //网络通信异常时,发送信号error(),连接到网络异常是执行的槽函数
    connect(&tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(onError()));
}

ClientDialog::~ClientDialog()
{
    delete ui;
}
//发送按钮对应的槽函数
void ClientDialog::on_sendButton_clicked()
{
    //获取用户输入的聊天消息
    QString msg = ui->messageEdit->text();
    if(msg == ""){
        return;
    }
    msg = username + ":" + msg;
    //发送聊天消息
    tcpSocket.write(msg.toUtf8());
    //清空消息输入框
    ui->messageEdit->clear();
}
//连接服务器按钮对应的槽函数
void ClientDialog::on_connectButton_clicked()
{
    if(status == false){//如果当前客户端是离线状态则建立连接
        //获取服务器IP
        if(serverIp.setAddress(ui->serverIpEdit->text()) == false){
            //如果IP设置错误,弹出消息提示框
            QMessageBox::critical(this,"Error","IP地址格式错误!");
            return;
        }
        //获取服务器端口
        serverPort = ui->serverPortEdit->text().toShort();
        if(serverPort < 1024){
            QMessageBox::critical(this,"Error","端口格式错误!");
            return;
        }
        //获取聊天室昵称
        username = ui->usernameEdit->text();
        if(username == ""){
            QMessageBox::critical(this,"Error","聊天室昵称不能为空!");
            return;
        }
        //向服务器发送连接请求
        //如果连接成功,发送信号connected
        //如果连接失败,发送信号error
        tcpSocket.connectToHost(serverIp,serverPort);
    }
    else{//如果当前客户端是在线状态则断开连接
        //向服务器离开服务器的消息提示
        QString msg = username + ":离开了聊天室";
        tcpSocket.write(msg.toUtf8());
        //断开和服务器连接,断开以后将会发送信号disconnected
        tcpSocket.disconnectFromHost();
    }
}
//和服务器连接成功时执行的槽函数
void ClientDialog::onConnected()
{
    qDebug() << "连接服务器成功!";
    status = true;//标记客户端位在线状态
    ui->sendButton->setEnabled(true);//设置"发送"按钮为可用状态
    ui->connectButton->setText("离开服务器");//修改"连接服务器"按钮文本为"离开服务器"
    ui->serverIpEdit->setEnabled(false);//禁用IP配置输入
    ui->serverPortEdit->setEnabled(false);//禁用端口配置输入
    ui->usernameEdit->setEnabled(false);//禁用昵称配置输入

    //向服务器发送进入聊天室提示消息
    QString msg = username + ":进入了聊天室";
    //toUtf8():将QString转换为QByteArray
    tcpSocket.write(msg.toUtf8());
}
//和服务器断开连接时执行的槽函数
void ClientDialog::onDisconnected()
{
    qDebug() << "已经断开了服务器!";
    status = false;//标记客户端位离线状态
    ui->sendButton->setEnabled(false);//设置"发送"按钮为禁用状态
    ui->connectButton->setText("连接服务器");//修改"离开服务器"按钮文本为"连接服务器"
    ui->serverIpEdit->setEnabled(true);//恢复IP配置输入
    ui->serverPortEdit->setEnabled(true);//恢复端口配置输入
    ui->usernameEdit->setEnabled(true);//恢复昵称配置输入
}
//接收聊天消息的槽函数
void ClientDialog::onReadRead()
{
    //bytesAvailable():获取等待读取消息的字节数
    if(tcpSocket.bytesAvailable()){
        //读取消息
        QByteArray buf = tcpSocket.readAll();
        //显示消息到界面
        ui->listWidget->addItem(buf);
        ui->listWidget->scrollToBottom();
    }
}
//网络异常是执行的槽函数
void ClientDialog::onError()
{
    //errorString:获取网络异常原因的字符串
    QMessageBox::critical(this,"Error",tcpSocket.errorString());
}

server.ui   -  服务器界面

QT网络编程_第6张图片

server.h  -   服务器

#ifndef SERVERDIALOG_H
#define SERVERDIALOG_H
#include 
#include  //QT += network
#include 
#include 
#include 
#include //列表容器
#include //定时器
namespace Ui {
class ServerDialog;
}
class ServerDialog : public QDialog
{
    Q_OBJECT
public:
    explicit ServerDialog(QWidget *parent = 0);
    ~ServerDialog();

private slots:
    //创建服务器按钮对应的槽函数
    void on_pushButton_clicked();
    //响应客户端连接请求的槽函数
    void onNewconnection();
    //接收聊天消息的槽函数
    void onReadyRead();
    //转发聊天消息的槽函数
    void sendMessage(const QByteArray& msg);
    //定时器处理的槽函数
    void onTimeout();
private:
    Ui::ServerDialog *ui;
    QTcpServer server;//TCP服务器
    quint16 serverPort;//服务器端口
    QList tcpClientList;//列表容器:用于保存所有客户端通信的套接字
    QTimer timer;//定时器,定时检查容器中的套接字的连接状态
};
#endif // SERVERDIALOG_H

server.c - 服务器实现
 

#include "serverdialog.h"
#include "ui_serverdialog.h"

ServerDialog::ServerDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::ServerDialog)
{
    ui->setupUi(this);
}

ServerDialog::~ServerDialog()
{
    delete ui;
}
//创建服务器按钮对应的槽函数
void ServerDialog::on_pushButton_clicked()
{
    //获取服务器端口
    serverPort = ui->lineEdit->text().toShort();
    //设置监听服务器和端口
    if(server.listen(QHostAddress::Any,serverPort) == false){
        qDebug() << "服务器创建失败!";
        return;
    }
    qDebug() << "服务器创建成功!";
    //服务器检测到有客户端连接时,发送信号newConnection,连接到响应客户端连接请求的槽函数
    connect(&server,SIGNAL(newConnection()),this,SLOT(onNewconnection()));
    //禁用创建服务器按钮和端口的输入
    ui->pushButton->setEnabled(false);
    ui->lineEdit->setEnabled(false);

    //定时器到时发送timeout信号,连接定时器处理的槽函数
    connect(&timer,SIGNAL(timeout()),this,SLOT(onTimeout()));
    //开始定时器,每隔3秒时间检查一次容器中是否存在断开连接的套接字
    timer.start(3000);
}
//响应客户端连接请求的槽函数
void ServerDialog::onNewconnection()
{
    //获取和客户端通信的套接字
    QTcpSocket* client =  server.nextPendingConnection();
    //将和客户端通信的套接字保存到容器中
    tcpClientList.append(client);
    //当客户端给服务器发送消息时,client发送信号readyReaad,连接到接收聊天消息的槽函数
    connect(client,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
}
//接收聊天消息的槽函数
void ServerDialog::onReadyRead()
{
    //遍历检查容器中哪个套接字有消息
    for(int i=0;ibytesAvailable()){
            //读取客户端发来的消息
            QByteArray buf = tcpClientList.at(i)->readAll();
            //显示消息到服务器界面
            ui->listWidget->addItem(buf);
            ui->listWidget->scrollToBottom();
            //转发消息给所有的客户端
            sendMessage(buf);
        }
    }
}
//转发聊天消息的槽函数
void ServerDialog::sendMessage(const QByteArray& msg)
{
    for(int i=0;iwrite(msg);
    }
}
//定时器处理的槽函数
void ServerDialog::onTimeout()
{
    for(int i=0;istate() == QAbstractSocket::UnconnectedState){
            //如果第i个套接字是断开连接的,则容器中删除
            tcpClientList.removeAt(i);
            --i;
        }
    }
}

四、HTTP

前两节中我们使用的QUdpSocket、QTcpSocket,QTcpServer类都是网络传输层上的类,他们封装了底层的网络进程通信(socket通信)功能。而QT网络应用开发则要在此基础上进一步实现应用型的协议功能。应用层的网络协议(HTTP/FTP/SMTP/POP3协议等)简称为“应用协议”,他们运行在TCP/UDP之上。

1、HTTP协议简介

HTTP协议简介 – 超文本传输协议(HyperText Transfer Protocol),是互联网上应用最为广泛的 一种网络协议,所有的HTML文件都必须遵守这个标准
– HTTP是一个客户端和服务器端请求和应答的标准,客户端是终端用户,服务器端是网站,客户端通过使用Web浏览器或者其它的工具,发起一个到服务器上 指定端口(默认端口为80)的HTTP请求。HTTP服务器监听客户端发送过来的 请求,一旦收到请求,服务器将发回一个状态行,比如“HTTP/1.1 200 OK”, 和响应消息,响应消息体可能是请求数据、文件、错误消息等。

2、HTTP协议请求

 HTTP协议请求(request)是由客户端向服务器发送的数据包,基于HTTP协议 的传输过程总是由客户端的请求开始,请求报文格式如下:
 
QT网络编程_第7张图片
QT网络编程_第8张图片   

3、HTTP协议响应

HTTP协议响应(response)是由服务器给客户端返回的消息,当服务器收到客 户端的请求以后,会根据请求内容,给客户端返回“适当”的数据
QT网络编程_第9张图片

4、QT网络应用开发相关类

QT4以前的版本提供了QHTTP、QFTP等类来构建客户端,从QT5开始,这些类就不在提供,所有应用层网络通信的API都是是围绕一个QNetworkAccessManager对象来进行,比如代理和缓存配置,以及处理相关的信号,监视网络的运行状态,网络相关 操作的应答信号,通过该类可以管理整个通信过程
QT网络应用开发相关类
QNetworkAccessManager:管理应用程序的发送请求和接收响应数据
QNetworkRequest:根据URL封装请求数据包
QNetworkReply:接收和请求对应的响应数据
 QUrl:网络地址(Uniform Resource Locator,即统一资源定位地址)
那么有了这几个类后QT再HTTP开发中就非常的简单,使用QNetworkAccessManager对象创建以后,应用程序可以使用它来在网络上发送的请求(QNetworkRequest),然后将返回一个用于接收响应数据的QNetworkReply对象。可以大致的看成三步。
//1、创建管理通信的manager对象
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
//2、根据URL初始化请求
QNetworkRequest request(QUrl(“http://qt-project.org”));
//3、使用get方法发送请求,并返回用于接收响应数据的reply对象指针
QNetworkReply *reply = manager->get(QNetworkRequest(url));

5、编程实现代码下载器,实现从“http://code.tarena.com.cn/”下载代码功能

HttpClent.ui  

QT网络编程_第10张图片

HttpClent.h  头文件

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include 
#include  //QT += network
#include 
#include 
#include 
#include  //处理登录认证
#include 
#include 
#include 
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
private:
    //向服务器发送请求
    void sendRequest();
private slots:
    //处理登录认证的槽函数
    void onAuthenticationRequired(QNetworkReply*,QAuthenticator*);
    //接收响应数据的槽函数
    void onReadyRead();
    //通信结束执行的槽函数
    void onFinished();
    //处理链接的槽函数
    void onAnchorClicked(const QUrl&);
    //下载文件
    void downloadFile(const QUrl&);
    //接收下载文件内容的槽函数
    void receiveFile();
    //更新显示文件下载进度的槽函数
    void onDownloadProgress(qint64,qint64);
    //文件下载完成执行的槽函数
    void receiveFileFinished();
private:
    Ui::MainWindow *ui;
    QNetworkAccessManager* manager;//管理HTTP通信
    QNetworkRequest request;//请求
    QNetworkReply* reply;//响应
    QByteArray buf;//保存响应数据的缓冲区
    QUrl currentUrl;//当前的URL地址
    QFile* file;//维护要下载文件
};
#endif // MAINWINDOW_H
HttpClent.c   -  实现
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //创建管理通信的Manager对象
    manager = new QNetworkAccessManager(this);
    //初始化请求和服务器通信的URL地址
    request.setUrl(QUrl("http://code.tarena.com.cn/"));
    //发送请求
    sendRequest();

    //点击文本浏览上面链接时发送信号anchorClicked,连接到处理链接的槽函数
    connect(ui->textBrowser,SIGNAL(anchorClicked(QUrl)),this,SLOT(onAnchorClicked(QUrl)));

}

MainWindow::~MainWindow()
{
    delete ui;
}
//向服务器发送请求
void MainWindow::sendRequest()
{
    //使用GET发送发送请求,返回用于接收响应数据的reply对象
    reply = manager->get(request);
    //如果连接服务器需要登录认证,将会发送信号authenticationRequired,连接到处理登录认证的槽函数
    connect(manager,SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
            this,SLOT(onAuthenticationRequired(QNetworkReply*,QAuthenticator*)));
    //认证成功后,服务器返回响应数据,reply将会发送信号readyRead,连接到接收响应数据的槽函数
    connect(reply,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
    //响应数据接收结束(通信结束),发送信号finished,连接到通信结束执行的槽函数
    connect(reply,SIGNAL(finished()),this,SLOT(onFinished()));
}
//处理登录认证的槽函数
void MainWindow::onAuthenticationRequired(QNetworkReply*,QAuthenticator* a)
{
    //qDebug() << "处理登录认证的槽函数";
    a->setUser("tarenacode");
    a->setPassword("code_2013");
}
//接收响应数据的槽函数
void MainWindow::onReadyRead()
{
    //qDebug() << "接收响应数据的槽函数";
    buf += reply->readAll();
}
//通信结束执行的槽函数
void MainWindow::onFinished()
{
    //qDebug() << "本次通信结束";
    ui->textBrowser->setText(buf);//将收到响应数据显示到界面
    buf.clear();//清空数据缓冲区
    currentUrl = reply->url();//记录当前所在的URL地址
    reply->deleteLater();//销毁reply对象

}
//处理链接的槽函数,参数点击的URL地址
void MainWindow::onAnchorClicked(const QUrl& url)
{
    //qDebug() << "当前URL:" << currentUrl.toString();
    //qDebug() << "点击URL:" << url.toString();
    //当前URL: "http://code.tarena.com.cn/"
    //点击URL: "CSDCode/"
    QUrl newUrl; //要进入新URL
    //如果点击不是"../",新URL = 当前URL+点击URL
    if(url.toString() != "../"){
        newUrl = currentUrl.toString() + url.toString();
    }
    else{//处理"../"
        //如果当前在首界面,点击"../"不做处理
        if(currentUrl.toString() == "http://code.tarena.com.cn/"){
            return;
        }
        //如果不在首界面,去掉最后一级目录链接,回到上级目录链接
        //当前URL: "http://code.tarena.com.cn/CSDCode/"
        //newUrl: "http://code.tarena.com.cn/"
        //查找倒数第二次出现"/"位置
        int pos = currentUrl.toString().lastIndexOf("/",-2);
        //字符串截断,从起始位置截断到倒数第二次出现"/"位置
        newUrl = currentUrl.toString().mid(0,pos+1);
    }
    //判断newUrl是否位文件链接,如果是文件则下载
    //判断是否为文件链接方式:不是目录就是文件,目录链接都带有"/"
    if(url.toString().lastIndexOf("/")==-1){
        downloadFile(newUrl);
        return;
    }
    //设置请求为新URL
    request.setUrl(newUrl);
    //发送请求
    sendRequest();
}
//下载文件
void MainWindow::downloadFile(const QUrl& fileUrl)
{
    //qDebug() << "下载文件:" << fileUrl.toString();
    //下载文件: "http://code.tarena.com.cn/CSDCode/csd1912/UC/day01.zip"
    //根据文件URL获取文件名
    QFileInfo fileInfo = fileUrl.path();//"/CSDCode/csd1912/UC/day01.zip"
    QString filename = fileInfo.fileName();//"day01.zip"
    //在本地创建同名的文件
    file = new QFile(filename);
    //以写的方式打开,如果文件不存在则创建
    file->open(QIODevice::WriteOnly);

    //设置请求位下载文件URL
    request.setUrl(fileUrl);
    //发送下载文件链接请求
    reply = manager->get(request);
    //收到响应数据(文件内容)时发送信号readyRead,连接接收下载文件内容的槽函数
    connect(reply,SIGNAL(readyRead()),this,SLOT(receiveFile()));
    //伴随文件下载,会发送下载进度信号downloadProgress,连接到更新显示文件下载进度的槽函数
    connect(reply,SIGNAL(downloadProgress(qint64,qint64)),
            this,SLOT(onDownloadProgress(qint64,qint64)));
    //文件下载完成,发送信号finished,连接文件下载完成执行的槽函数
    connect(reply,SIGNAL(finished()),this,SLOT(receiveFileFinished()));
}
//接收下载文件内容的槽函数
void MainWindow::receiveFile()
{
    //读取响应数据(文件内容)并写入本地的文件
    file->write(reply->readAll());
}
//更新显示文件下载进度的槽函数,参数:(1)已收到的字节数 (2)总字节数
void MainWindow::onDownloadProgress(qint64 bytesRead,qint64 bytesTotal)
{
    //计算百分比
    qint64 progress = bytesRead*100/bytesTotal;
    //打印百分比
    qDebug() << file->fileName() << ":" << progress << "%...";
}
//文件下载完成执行的槽函数
void MainWindow::receiveFileFinished()
{
    qDebug() << file->fileName() << ":文件下载完成";
    file->flush();//刷新文件流
    file->close();
    reply->deleteLater();
}
//注:目前只能一次下载一个文件
//扩展:
//(1)在认证登录位置,增加登录对话框
//(2)将下载文件操作,放到子线程中执行,实现多个文件的同时下载
//(3)下载文件时.指定保存在"/home/tarena/Downloads"
//参考:refer.tar.gz

 

 

你可能感兴趣的:(QT,qt,网络)