ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

我的QT Creator学习笔记(三十五)——网络编程之UDP与TCP

2021-06-14 20:02:39  阅读:317  来源: 互联网

标签:UDP QT Creator void qint64 Server Client ui include


 参考文献:《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运行效果

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

标签:UDP,QT,Creator,void,qint64,Server,Client,ui,include
来源: https://blog.csdn.net/sinat_41928334/article/details/117909801

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有