我的QT Creator学习笔记(三十五)——网络编程之UDP与TCP
2021/6/14 20:20:59
本文主要是介绍我的QT Creator学习笔记(三十五)——网络编程之UDP与TCP,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
参考文献:《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运行效果
这篇关于我的QT Creator学习笔记(三十五)——网络编程之UDP与TCP的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-26Rocket消息中间件教程:新手入门详解
- 2024-11-26RocketMQ项目开发教程:新手入门指南
- 2024-11-26MQ源码教程:轻松入门Apache MQ源码解析
- 2024-11-26Rocket消息队列教程:新手入门必读
- 2024-11-26Rocket消息队列教程:新手入门指南
- 2024-11-26RocketMQ底层原理教程:新手入门指南
- 2024-11-26RocketMQ底层原理教程:入门级详解
- 2024-11-26如何获取 OpenAI API Key 用于ChatGPT AI大模型开发?
- 2024-11-26MATLAB 中 A(7)=[];什么意思?-icode9专业技术文章分享
- 2024-11-26UniApp 中如何实现使用输入法时保持页面列表不动的效果?-icode9专业技术文章分享