参考文献:《Qt Creator 快速入门》第三版 霍亚飞编著
1、UDP
UDP(User Datagram Protocol,用户数据报协议)是一个轻量级的、不可靠的、面向数据报的、无连接的协议,用于可靠性不是非常重要的情况。UDP一般分为发送端和接收端。
QUdpSocket类用来发送和接收UDP数据报,继承自QAbstractSocket类。这里的socket就是所谓的“套接字”,简单来说“套接字”就是一个IP地址加一个port端口号。
1.1UDP编程示例
下面是一个UDP编程示例,实现的功能:发送端指定端口号,输入要发送的内容,点击广播按钮发送。接收端指定接收端口号,并显示接收到的数据。
1.1 .1udp发送端
发送端用到的主要接口qint64 QUdpSocket::writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port);,该函数用来发送数据报。
发送端ui
发送端完整头文件
#ifndef SENDER_H
#define SENDER_H
#include <QDialog>
QT_BEGIN_NAMESPACE
namespace Ui { class Sender; }
QT_END_NAMESPACE
class QUdpSocket;
class Sender : public QDialog
{
Q_OBJECT
public:
Sender(QWidget *parent = nullptr);
~Sender();
private slots:
void on_pushButton_clicked();
private:
Ui::Sender *ui;
QUdpSocket* sender;
};
#endif // SENDER_H
发送端完整.cpp文件
#include "sender.h"
#include "ui_sender.h"
#include <QUdpSocket>
#include <QtNetwork>
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=ui->textEdit->toPlainText().toLocal8Bit();
sender->writeDatagram(datagram.data(),datagram.size(),QHostAddress::Broadcast,ui->spinBox->value());
}
1.1.2 udp接收端
接收端用到的主要接口:
bool QAbstractSocket::bind(quint16 port = 0, BindMode mode = DefaultForPlatform)绑定端口。该接口不需要指定IP,默认支持所有IPv4的IP地址。第一个参数指定的端口号要与发送端一致。第二个参数是绑定模式。QUdpSocket::ShareAddress表明允许其他服务器绑定到相同的地址和端口上。
每当有数据报到来时,QUdpSocket都会发射readyRead()信号,这样就可以在自定义的槽中读取数据。
bool QUdpSocket::hasPendingDatagrams() const判断是否还有等待读取的数据。
qint64 QUdpSocket::readDatagram(char *data, qint64 maxlen, QHostAddress *host = Q_NULLPTR, quint16 *port = Q_NULLPTR)接收数据。
接收端ui
接收端完整.h文件
#ifndef RECEIVER_H
#define RECEIVER_H
#include <QDialog>
QT_BEGIN_NAMESPACE
namespace Ui { class Receiver; }
QT_END_NAMESPACE
class QUdpSocket;
class Receiver : public QDialog
{
Q_OBJECT
public:
Receiver(QWidget *parent = nullptr);
~Receiver();
private slots:
void processPendingDatagram();
void on_spinBox_valueChanged(int arg1);
private:
Ui::Receiver *ui;
QUdpSocket* receiver;
};
#endif // RECEIVER_H
接收端完整.cpp文件
#include "receiver.h"
#include "ui_receiver.h"
#include<QtNetwork>
Receiver::Receiver(QWidget *parent)
: QDialog(parent)
, ui(new Ui::Receiver)
{
ui->setupUi(this);
receiver=new QUdpSocket(this);
receiver->bind(ui->spinBox->value(),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);
ui->textBrowser->setText(QString::fromLocal8Bit( datagram));
}
}
void Receiver::on_spinBox_valueChanged(int arg1)
{
receiver->close();
receiver->bind(arg1,QUdpSocket::ShareAddress);
}
1.1.3运行效果
2、TCP
TCP(Transmission Control Protocol,传输控制协议)是一个用于数据传输的低层的网络协议,多个互联网协议(包括HTTP和FTP)都是基于TCP协议的。TCP是一个面向数据流和连接的可靠的传输协议。
QTcpSocket类也继承自QAbstractSocket类。与QUdpSocket传输的数据报不同,QTcpSocket传输的是连续的数据流,尤其适合于连续数据传输。TCP编程一般分为客户端和服务器端,也就是所谓的C/S(Client/Server)模型。
在任何数据传输之前,必须建立一个TCP连接到远程的主机和端口上。
QTcpSocket是异步进行工作的,通过发射信号来报告状态改变和错误信息。
可以使用QTcpSocket::write()函数来写入数据,使用QTcpSocket::read()函数来读取数据。当从一个QTcpSocket中读取数据前,必须先调用QTcpSocket::bytesAvailable()函数来确保已经有足够的数据可用。
如果要处理到来的TCP连接,则可以使用QTcpSocket类调用listen()函数来设置服务器,然后关联newConnection()信号到自定义槽,每当有客户端连接时都会发射该信号。然后再自定义槽中调用nextPendingConnection()来接收这个连接,使用该函数返回的QTcpSocket对象与客户端进行通信。(详见下面示例代码中Server::acceptConnection槽函数)
2.1 TCP编程示例
下面是一个TCP编程示例,实现的功能:实现大型文件的传输,并显示传输进度。
2.1.1 tcp客户端
客户端主要功能及实现方式:从ui界面指定要连接的服务器地址以及端口号,选择要发送的文件。点击发送按钮后,调用void QAbstractSocket::connectToHost()函数连接到服务器。当连接到服务器后会收到QAbstractSocket::connected()信号,预先将该信号关联到自定义槽startTransfer()上,自定义槽中调用qint64 QTcpSocket::write()发送文件头结构。每次发送完成后会收到bytesWritten信号,将该信号关联到自定义槽updateClientProgress槽上,在该槽函数中分块发送数据并更新进度条。
客户端ui
客户端完整.h文件
#ifndef CLIENT_H
#define CLIENT_H
#include <QDialog>
#include <QAbstractSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Client; }
QT_END_NAMESPACE
class QTcpSocket;
class QFile;
class Client : public QDialog
{
Q_OBJECT
public:
Client(QWidget *parent = nullptr);
~Client();
private slots:
void openFile();
void send();
void startTransfer();
void updateClientProgress(qint64);
void displayError(QAbstractSocket::SocketError error);
void on_openButton_clicked();
void on_sendButton_clicked();
private:
Ui::Client *ui;
QTcpSocket* tcpClient;
QFile* localFile;//要发送的文件
qint64 totalBytes;//发送数据总大小
qint64 bytesWritten;//已经发送数据大小
qint64 bytesToWrite;//剩余数据大小
qint64 payloadSize;//每次发送数据的大小
QString fileName;//保存文件路径
QByteArray outBlock;//数据缓冲区,即存放每次要发送的数据块
};
#endif // CLIENT_H
客户端完整.cpp文件
#include "client.h"
#include "ui_client.h"
#include <QtNetwork>
#include <QFileDialog>
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(QString::fromLocal8Bit("打开文件%1成功!").arg(fileName));
}
}
void Client::send()
{
ui->sendButton->setEnabled(false);
//初始化已发送字节为0
bytesWritten=0;
ui->clientStatusLabel->setText(QString::fromLocal8Bit("连接中..."));
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_5_6);
QString currentFileName=fileName.right(fileName.size()-fileName.lastIndexOf('/')-1);
//保留总大小信息空间、文件名大小信息空间然后输入文件名
sendOut<<qint64(0)<<qint64(0)<<currentFileName;
//这里的总大小是总大小信息、文件名大小信息、文件名和实际文件大小的总和
totalBytes+=outBlock.size();
//返回outBlock的开始,用实际的大小信息,代替两个qint64(0)空间
sendOut.device()->seek(0);
sendOut<<totalBytes<<qint64(outBlock.size()-sizeof(qint64)*2);
//发送完文件头结构后剩余数据的大小
bytesToWrite=totalBytes-tcpClient->write(outBlock);
ui->clientStatusLabel->setText(QString::fromLocal8Bit("已连接"));
outBlock.resize(0);
}
void Client::updateClientProgress(qint64 numBytes)
{
//已经发送数据的大小
bytesWritten+=(int)numBytes;
//如果已经发送了数据
if(bytesToWrite>0)
{
//每次发送payloadSize大小的数据64KB,如果剩余的数据不足64K,就发送剩余数据大小
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(QString::fromLocal8Bit("传送文件%1成功").arg(fileName));
localFile->close();
tcpClient->close();
}
}
void Client::displayError(QAbstractSocket::SocketError error)
{
qDebug()<<tcpClient->errorString();
tcpClient->close();
ui->clientProgressBar->reset();
ui->clientStatusLabel->setText(QString::fromLocal8Bit("客户端就绪"));
ui->sendButton->setEnabled(true);
}
void Client::on_openButton_clicked()
{
ui->clientProgressBar->reset();
ui->clientStatusLabel->setText(QString::fromLocal8Bit("状态:等待打开文件"));
openFile();
}
void Client::on_sendButton_clicked()
{
send();
}
2.1.2 tcp服务器端
服务器端主要功能及实现方式:单击“开始监听”按钮后,调用bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)接口开启监听。将QTcpServer::newConnection关联到自定义槽acceptConnection()上。在acceptConnection()槽函数中,接收到来的连接请求,并获取其套接字tcpServerConnection=tcpServer.nextPendingConnection();然后进行信号槽关联,readyRead信号关联到updateServerProgress()槽上。在updateServerProgress()槽函数中先分别接收总数居大小、文件名大小以及文件名等文件头结构信息,再接收实际的文件,然后更新进度条。用到的主要接口tcpServerConnection->bytesAvailable()、tcpServerConnection->readAll()
服务器端ui
服务器端完整.h文件
#ifndef SERVER_H
#define SERVER_H
#include <QDialog>
#include <QAbstractSocket>
#include <QTcpServer>
QT_BEGIN_NAMESPACE
namespace Ui { class Server; }
QT_END_NAMESPACE
class QTcpSocket;
class QFile;
class Server : public QDialog
{
Q_OBJECT
public:
Server(QWidget *parent = nullptr);
~Server();
private slots:
void start();
void acceptConnection();
void updateServerProgress();
void displayError(QAbstractSocket::SocketError socketError);
void on_startButton_clicked();
private:
Ui::Server *ui;
QTcpServer tcpServer;
QTcpSocket* tcpServerConnection;
qint64 totalBytes;//存放总大小信息
qint64 bytesReceived;//已收到数据的大小
qint64 fileNameSize;//文件名大小信息
QString fileName;//存放文件名
QFile* localFile;//本地文件
QByteArray inBlock;//数据缓冲区
};
#endif // SERVER_H
服务器端完整.cpp文件
#include "server.h"
#include "ui_server.h"
#include <QtNetwork>
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(QString::fromLocal8Bit("监听"));
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(QString::fromLocal8Bit("接收连接"));
//关闭服务器不再进行监听
tcpServer.close();
}
void Server::updateServerProgress()
{
QDataStream in(tcpServerConnection);
in.setVersion(QDataStream::Qt_5_6);
//如果接收到的数据小于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(QString::fromLocal8Bit("接收文件%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(QString::fromLocal8Bit("接收文件%1成功").arg(fileName));
}
}
void Server::displayError(QAbstractSocket::SocketError socketError)
{
qDebug()<<tcpServerConnection->errorString();
tcpServerConnection->close();
ui->serverProgressBar->reset();
ui->ServerStatusLabel->setText(QString::fromLocal8Bit("服务器端就绪"));
ui->startButton->setEnabled(true);
}
void Server::on_startButton_clicked()
{
start();
}
2.1.3运行效果