Qt中的Qt Network模块用来编写基于TCP/IP的网络程序。
项目文件中需要添加
QT+=network
帮助关键字Network Programming with Qt
网络请求 QNetworkRequest,支持HTTP、FTP和本地文件的URL的上传和下载。
协调网络操作 QNetworkAccessManager类。
网络请求的应答 QNetworkReply。
超文本传输协议(HyperText Transfer Protocol)是一个客户端和服务器端之间进行请求和应答的标准。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished,
this, &MainWindow::replyFinished);
manager->get(QNetworkRequest(QUrl("http://www.qter.org")));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::replyFinished(QNetworkReply *reply)
{
QString all = reply->readAll();
ui->textBrowser->setText(all);
reply->deleteLater();
}
一般文件下载例子:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
manager = new QNetworkAccessManager(this);
ui->progressBar->hide();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::startRequest(QUrl url)
{
reply = manager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::readyRead, this, &MainWindow::httpReadyRead);
connect(reply, &QNetworkReply::downloadProgress,
this, &MainWindow::updateDataReadProgress);
connect(reply, &QNetworkReply::finished, this, &MainWindow::httpFinished);
}
void MainWindow::httpReadyRead()
{
if (file) file->write(reply->readAll());
}
void MainWindow::updateDataReadProgress(qint64 bytesRead, qint64 totalBytes)
{
ui->progressBar->setMaximum(totalBytes);
ui->progressBar->setValue(bytesRead);
}
void MainWindow::httpFinished()
{
ui->progressBar->hide();
if(file) {
file->close();
delete file;
file = 0;
}
reply->deleteLater();
reply = 0;
}
void MainWindow::on_pushButton_clicked()
{
url = ui->lineEdit->text();
QFileInfo info(url.path());
QString fileName(info.fileName());
if (fileName.isEmpty()) fileName = "index.html";
file = new QFile(fileName);
if(!file->open(QIODevice::WriteOnly))
{
delete file;
file = 0;
return;
}
startRequest(url);
ui->progressBar->setValue(0);
ui->progressBar->show();
}
文件传输协议(File Transfer Protocol)是一个主要用于浏览远程目录和传输文件的协议。
Qt 5中废弃了QFtp类,但是可以在兼容扩展模块中找到QFtp类。
简单示例:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "qftp.h"
#include
#include
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->progressBar->setValue(0);
connect(ui->fileList, &QTreeWidget::itemActivated,
this, &MainWindow::processItem);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::ftpCommandStarted(int)
{
int id = ftp->currentCommand();
switch (id)
{
case QFtp::ConnectToHost :
ui->label->setText(tr("正在连接到服务器…"));
break;
case QFtp::Login :
ui->label->setText(tr("正在登录…"));
break;
case QFtp::Get :
ui->label->setText(tr("正在下载…"));
break;
case QFtp::Close :
ui->label->setText(tr("正在关闭连接…"));
}
}
void MainWindow::ftpCommandFinished(int, bool error)
{
if(ftp->currentCommand() == QFtp::ConnectToHost) {
if (error)
ui->label->setText(tr("连接服务器出现错误:%1").arg(ftp->errorString()));
else ui->label->setText(tr("连接到服务器成功"));
} else if (ftp->currentCommand() == QFtp::Login) {
if (error)
ui->label->setText(tr("登录出现错误:%1").arg(ftp->errorString()));
else {
ui->label->setText(tr("登录成功"));
ftp->list();
}
} else if (ftp->currentCommand() == QFtp::Get) {
if(error)
ui->label->setText(tr("下载出现错误:%1").arg(ftp->errorString()));
else {
ui->label->setText(tr("已经完成下载"));
file->close();
}
ui->downloadButton->setEnabled(true);
} else if (ftp->currentCommand() == QFtp::List) {
if (isDirectory.isEmpty())
{
ui->fileList->addTopLevelItem(
new QTreeWidgetItem(QStringList()<< tr("" )));
ui->fileList->setEnabled(false);
ui->label->setText(tr("该目录为空"));
}
} else if (ftp->currentCommand() == QFtp::Close) {
ui->label->setText(tr("已经关闭连接"));
}
}
// 连接按钮
void MainWindow::on_connectButton_clicked()
{
ui->fileList->clear();
currentPath.clear();
isDirectory.clear();
ui->progressBar->setValue(0);
ftp = new QFtp(this);
connect(ftp, &QFtp::commandStarted, this,
&MainWindow::ftpCommandStarted);
connect(ftp, &QFtp::commandFinished,
this, &MainWindow::ftpCommandFinished);
connect(ftp, &QFtp::listInfo, this, &MainWindow::addToList);
connect(ftp, &QFtp::dataTransferProgress,
this, &MainWindow::updateDataTransferProgress);
QString ftpServer = ui->ftpServerLineEdit->text();
QString userName = ui->userNameLineEdit->text();
QString passWord = ui->passWordLineEdit->text();
ftp->connectToHost(ftpServer, 21);
ftp->login(userName, passWord);
}
void MainWindow::addToList(const QUrlInfo &urlInfo)
{
// 注意:因为服务器上文件使用UTF-8编码,所以要进行编码转换,这样显示中文才不会乱码
QString name = QString::fromUtf8(urlInfo.name().toLatin1());
QString owner = QString::fromUtf8(urlInfo.owner().toLatin1());
QString group = QString::fromUtf8(urlInfo.group().toLatin1());
QTreeWidgetItem *item = new QTreeWidgetItem;
item->setText(0, name);
item->setText(1, QString::number(urlInfo.size()));
item->setText(2, owner);
item->setText(3, group);
item->setText(4, urlInfo.lastModified().toString("yyyy-MM-dd"));
QPixmap pixmap(urlInfo.isDir() ? "../myFTP/dir.png" : "../myFTP/file.png");
item->setIcon(0, pixmap);
isDirectory[name] = urlInfo.isDir();
ui->fileList->addTopLevelItem(item);
if (!ui->fileList->currentItem()) {
ui->fileList->setCurrentItem(ui->fileList->topLevelItem(0));
ui->fileList->setEnabled(true);
}
}
void MainWindow::processItem(QTreeWidgetItem *item, int)
{
// 如果这个文件是个目录,则打开
if (isDirectory.value(item->text(0))) {
// 注意:因为目录名称可能是中文,在使用ftp命令cd()前需要先进行编码转换
QString name = QLatin1String(item->text(0).toUtf8());
ui->fileList->clear();
isDirectory.clear();
currentPath += "/";
currentPath += name;
ftp->cd(name);
ftp->list();
ui->cdToParentButton->setEnabled(true);
}
}
// 返回上级目录按钮
void MainWindow::on_cdToParentButton_clicked()
{
ui->fileList->clear();
isDirectory.clear();
currentPath = currentPath.left(currentPath.lastIndexOf('/'));
if (currentPath.isEmpty()) {
ui->cdToParentButton->setEnabled(false);
ftp->cd("/");
} else {
ftp->cd(currentPath);
}
ftp->list();
}
// 下载按钮
void MainWindow::on_downloadButton_clicked()
{
// 注意:因为文件名称可能是中文,所以在使用get()函数前需要进行编码转换
QString fileName = ui->fileList->currentItem()->text(0);
QString name = QLatin1String(fileName.toUtf8());
file = new QFile(fileName);
if (!file->open(QIODevice::WriteOnly)) {
delete file;
return;
}
ui->downloadButton->setEnabled(false);
ftp->get(name, file);
}
void MainWindow::updateDataTransferProgress(qint64 readBytes,qint64 totalBytes)
{
ui->progressBar->setMaximum(totalBytes);
ui->progressBar->setValue(readBytes);
}
进行TCP/UDP编程时,需要先将连接的主机名解析为IP(互联网协议)地址,可以使用DNS(域名服务)协议执行,IP(互联网协议)有IPV4和IPV6两个版本。
QtNetwork模块中的QHostInfo类提供了静态函数可以进行主机名的查找。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QString localHostName = QHostInfo::localHostName();
QHostInfo info = QHostInfo::fromName(localHostName);
qDebug() << "localHostName: " << localHostName << endl
<< "IP Address: " << info.addresses();
foreach (QHostAddress address, info.addresses())
{
if(address.protocol() == QAbstractSocket::IPv4Protocol)
qDebug() << address.toString();
}
QHostInfo::lookupHost("www.baidu.com", this, SLOT(lookedUp(QHostInfo)));
// 获取所有网络接口的列表
QList list = QNetworkInterface::allInterfaces();
// 遍历每一个网络接口
foreach (QNetworkInterface interface, list)
{
// 接口名称
qDebug() << "Name: " << interface.name();
// 硬件地址
qDebug() << "HardwareAddress: " << interface.hardwareAddress();
// 获取IP地址条目列表,每个条目包含一个IP地址,一个子网掩码和一个广播地址
QList entryList = interface.addressEntries();
// 遍历每一个IP地址条目
foreach (QNetworkAddressEntry entry, entryList)
{
// IP地址
qDebug() << "IP Address: " << entry.ip().toString();
// 子网掩码
qDebug() << "Netmask: " << entry.netmask().toString();
// 广播地址
qDebug() << "Broadcast: " << entry.broadcast().toString();
}
}
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::lookedUp(const QHostInfo &host)
{
if (host.error() != QHostInfo::NoError) {
qDebug() << "Lookup failed:" << host.errorString();
return;
}
foreach (const QHostAddress &address, host.addresses())
qDebug() << "Found address:" << address.toString();
}
用户数据报协议(User Datagram Protocol)是一个轻量级、不可靠的、面向数据报的、无连接的协议,用于可靠性要求不高的场合。
QUdpSocket类用来发送和接收UDP数据报。支持IPV4广播。
Socket就是套接字,简单理解就是一个IP地址加一个port端口号。
发送的例子
#include "sender.h"
#include "ui_sender.h"
#include
Sender::Sender(QWidget *parent) :
QDialog(parent),
ui(new Ui::Sender)
{
ui->setupUi(this);
sender = new QUdpSocket(this);
}
Sender::~Sender()
{
delete ui;
}
void Sender::on_pushButton_clicked()
{
QByteArray datagram = "hello world!";
sender->writeDatagram(datagram.data(), datagram.size(),
QHostAddress::Broadcast, 45454);
}
接收
#include "receiver.h"
#include "ui_receiver.h"
#include
Receiver::Receiver(QWidget *parent) :
QDialog(parent),
ui(new Ui::Receiver)
{
ui->setupUi(this);
receiver = new QUdpSocket(this);
receiver->bind(45454, QUdpSocket::ShareAddress);
connect(receiver, &QUdpSocket::readyRead, this, &Receiver::processPendingDatagram);
}
Receiver::~Receiver()
{
delete ui;
}
void Receiver::processPendingDatagram()
{
// 拥有等待的数据报
while(receiver->hasPendingDatagrams())
{
QByteArray datagram;
// 让datagram的大小为等待处理的数据报的大小,这样才能接收到完整的数据
datagram.resize(receiver->pendingDatagramSize());
// 接收数据报,将其存放到datagram中
receiver->readDatagram(datagram.data(), datagram.size());
ui->label->setText(datagram);
}
}
传输控制协议(Transmission Control Protocol)是一个用于数据传输的底层的网络协议,多个网络协议都是基于TCP协议的,是一个面向数据流和连接的可靠的传输协议。
QTcpSocket类为TCP提供了一个接口,可以实现POP3、SMTP和NNTP等标准的网络协议,也可以实现自定义的网络协议。
服务器端:
#include "server.h"
#include "ui_server.h"
#include
Server::Server(QWidget *parent) :
QDialog(parent),
ui(new Ui::Server)
{
ui->setupUi(this);
connect(&tcpServer, SIGNAL(newConnection()),
this, SLOT(acceptConnection()));
}
Server::~Server()
{
delete ui;
}
void Server::start()
{
if (!tcpServer.listen(QHostAddress::LocalHost, 6666)) {
qDebug() << tcpServer.errorString();
close();
return;
}
ui->startButton->setEnabled(false);
totalBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
ui->serverStatusLabel->setText(tr("监听"));
ui->serverProgressBar->reset();
}
void Server::acceptConnection()
{
tcpServerConnection = tcpServer.nextPendingConnection();
connect(tcpServerConnection, SIGNAL(readyRead()),
this, SLOT(updateServerProgress()));
connect(tcpServerConnection, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(displayError(QAbstractSocket::SocketError)));
ui->serverStatusLabel->setText(tr("接受连接"));
// 关闭服务器,不再进行监听
tcpServer.close();
}
void Server::updateServerProgress()
{
QDataStream in(tcpServerConnection);
in.setVersion(QDataStream::Qt_4_0);
// 如果接收到的数据小于16个字节,保存到来的文件头结构
if (bytesReceived <= sizeof(qint64)*2) {
if((tcpServerConnection->bytesAvailable() >= sizeof(qint64)*2)
&& (fileNameSize == 0)) {
// 接收数据总大小信息和文件名大小信息
in >> totalBytes >> fileNameSize;
bytesReceived += sizeof(qint64) * 2;
}
if((tcpServerConnection->bytesAvailable() >= fileNameSize)
&& (fileNameSize != 0)) {
// 接收文件名,并建立文件
in >> fileName;
ui->serverStatusLabel->setText(tr("接收文件 %1 …")
.arg(fileName));
bytesReceived += fileNameSize;
localFile = new QFile(fileName);
if (!localFile->open(QFile::WriteOnly)) {
qDebug() << "server: open file error!";
return;
}
} else {
return;
}
}
// 如果接收的数据小于总数据,那么写入文件
if (bytesReceived < totalBytes) {
bytesReceived += tcpServerConnection->bytesAvailable();
inBlock = tcpServerConnection->readAll();
localFile->write(inBlock);
inBlock.resize(0);
}
ui->serverProgressBar->setMaximum(totalBytes);
ui->serverProgressBar->setValue(bytesReceived);
// 接收数据完成时
if (bytesReceived == totalBytes) {
tcpServerConnection->close();
localFile->close();
ui->startButton->setEnabled(true);
ui->serverStatusLabel->setText(tr("接收文件 %1 成功!")
.arg(fileName));
}
}
void Server::displayError(QAbstractSocket::SocketError socketError)
{
qDebug() << tcpServerConnection->errorString();
tcpServerConnection->close();
ui->serverProgressBar->reset();
ui->serverStatusLabel->setText(tr("服务端就绪"));
ui->startButton->setEnabled(true);
}
// 开始监听按钮
void Server::on_startButton_clicked()
{
start();
}
客户端:
#include "client.h"
#include "ui_client.h"
#include
#include
Client::Client(QWidget *parent) :
QDialog(parent),
ui(new Ui::Client)
{
ui->setupUi(this);
payloadSize = 64*1024; // 64KB
totalBytes = 0;
bytesWritten = 0;
bytesToWrite = 0;
tcpClient = new QTcpSocket(this);
// 当连接服务器成功时,发出connected()信号,开始传送文件
connect(tcpClient, SIGNAL(connected()), this, SLOT(startTransfer()));
connect(tcpClient, SIGNAL(bytesWritten(qint64)),
this, SLOT(updateClientProgress(qint64)));
connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(displayError(QAbstractSocket::SocketError)));
ui->sendButton->setEnabled(false);
}
Client::~Client()
{
delete ui;
}
void Client::openFile()
{
fileName = QFileDialog::getOpenFileName(this);
if (!fileName.isEmpty()) {
ui->sendButton->setEnabled(true);
ui->clientStatusLabel->setText(tr("打开文件 %1 成功!").arg(fileName));
}
}
void Client::send()
{
ui->sendButton->setEnabled(false);
// 初始化已发送字节为0
bytesWritten = 0;
ui->clientStatusLabel->setText(tr("连接中…"));
tcpClient->connectToHost(ui->hostLineEdit->text(),
ui->portLineEdit->text().toInt());
}
void Client::startTransfer()
{
localFile = new QFile(fileName);
if (!localFile->open(QFile::ReadOnly)) {
qDebug() << "client: open file error!";
return;
}
// 获取文件大小
totalBytes = localFile->size();
QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_4_0);
QString currentFileName = fileName.right(fileName.size()
- fileName.lastIndexOf('/')-1);
// 保留总大小信息空间、文件名大小信息空间,然后输入文件名
sendOut << qint64(0) << qint64(0) << currentFileName;
// 这里的总大小是总大小信息、文件名大小信息、文件名和实际文件大小的总和
totalBytes += outBlock.size();
sendOut.device()->seek(0);
// 返回outBolock的开始,用实际的大小信息代替两个qint64(0)空间
sendOut << totalBytes << qint64((outBlock.size() - sizeof(qint64)*2));
// 发送完文件头结构后剩余数据的大小
bytesToWrite = totalBytes - tcpClient->write(outBlock);
ui->clientStatusLabel->setText(tr("已连接"));
outBlock.resize(0);
}
void Client::updateClientProgress(qint64 numBytes)
{
// 已经发送数据的大小
bytesWritten += (int)numBytes;
// 如果已经发送了数据
if (bytesToWrite > 0) {
// 每次发送payloadSize大小的数据,这里设置为64KB,如果剩余的数据不足64KB,
// 就发送剩余数据的大小
outBlock = localFile->read(qMin(bytesToWrite, payloadSize));
// 发送完一次数据后还剩余数据的大小
bytesToWrite -= (int)tcpClient->write(outBlock);
// 清空发送缓冲区
outBlock.resize(0);
} else { // 如果没有发送任何数据,则关闭文件
localFile->close();
}
// 更新进度条
ui->clientProgressBar->setMaximum(totalBytes);
ui->clientProgressBar->setValue(bytesWritten);
// 如果发送完毕
if(bytesWritten == totalBytes) {
ui->clientStatusLabel->setText(tr("传送文件 %1 成功").arg(fileName));
localFile->close();
tcpClient->close();
}
}
void Client::displayError(QAbstractSocket::SocketError)
{
qDebug() << tcpClient->errorString();
tcpClient->close();
ui->clientProgressBar->reset();
ui->clientStatusLabel->setText(tr("客户端就绪"));
ui->sendButton->setEnabled(true);
}
// 打开按钮
void Client::on_openButton_clicked()
{
ui->clientProgressBar->reset();
ui->clientStatusLabel->setText(tr("状态:等待打开文件!"));
openFile();
}
// 发送按钮
void Client::on_sendButton_clicked()
{
send();
}
网络相关的还有网络代理QNetworkProxy类、承载管理QNetworkConfigurationManager类、QNetworkSession类以及通信安全的QSsl相关类等等。