My QT Creator learning notes -- UDP and TCP in network programming

Posted by []InTeR[] on Mon, 31 Jan 2022 08:23:40 +0100

Reference: Qt Creator quick start, Third Edition, edited by Huo Yafei

1,UDP

UDP(User Datagram Protocol) is a lightweight, unreliable, datagram oriented and connectionless protocol, which is used when reliability is not very important. UDP is generally divided into sender and receiver.

QUdpSocket class is used to send and receive UDP datagrams, which is inherited from QAbstractSocket class. Socket here is the so-called "socket". In short, "socket" is an IP address plus a port number.

 

1.1 UDP programming example

The following is a UDP programming example, which realizes the following functions: the sender specifies the port number, enters the content to be sent, and clicks the broadcast button to send. The receiving end specifies the receiving port number and displays the received data.

1.1.1 UDP sender

The main interface used by the sender is qint64 qudpsocket:: writedatagram (const char * data, qint64 len, const qhostaddress & host, quant16 port);, This function is used to send datagrams.

Sender ui

Sending end full header file

#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

The sender is complete cpp file

#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 receiver

Main interfaces used at the receiving end:

Bool qabstractsocket:: bind (quint16 port = 0, bindmode = defaultforplatform) bind port. This interface does not need to specify an IP address. It supports all IPv4 IP addresses by default. The port number specified in the first parameter should be consistent with the sender. The second parameter is the binding mode. QUdpSocket::ShareAddress indicates that other servers are allowed to bind to the same address and port.

Whenever a datagram arrives, QUdpSocket will send readyRead() signal, so that data can be read in a customized slot.

bool QUdpSocket::hasPendingDatagrams() const judge whether there is still data waiting to be read.

Qint64 qudpsocket:: readdatagram (char * data, qint64 maxlen, qhostaddress * host = q_nullptr, qint16 * port = q_nullptr) receives data.

Receiver ui

The receiver is complete h file

#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

The receiver is complete cpp file

#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()
{
    //Have waiting datagrams
    while(receiver->hasPendingDatagrams())
    {
        QByteArray datagram;
        //Let the size of datagram be the size of datagram waiting to be processed, so as to receive complete data
        datagram.resize(receiver->pendingDatagramSize());
        //Receive datagrams and store them in 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 operation effect

 

2,TCP

TCP (Transmission Control Protocol) is a low-level network protocol for data transmission. Many Internet Protocols (including HTTP and FTP) are based on TCP protocol. TCP is a reliable transmission protocol for data flow and connection.

The QTcpSocket class also inherits from the QAbstractSocket class. Unlike the datagram transmitted by QUdpSocket, QTcpSocket transmits continuous data stream, which is especially suitable for continuous data transmission. TCP programming is generally divided into client and server, that is, the so-called C/S (Client/Server) model.

Before any data transmission, a TCP connection must be established to the remote host and port.

QTcpSocket works asynchronously and reports status changes and error messages by transmitting signals.

You can use QTcpSocket::write() function to write data and QTcpSocket::read() function to read data. Before reading data from a QTcpSocket, you must first call the QTcpSocket::bytesAvailable() function to ensure that there is enough data available.

If you want to handle the incoming TCP connection, you can use the qtcpsocket class to call the listen() function to set up the server, and then associate the newConnection() signal to the custom slot, which will be transmitted whenever there is a client connection. Then call the nextPendingConnection() in the custom slot to receive the connection, and use the QTcpSocket object returned by the function to communicate with the client. (see the Server::acceptConnection slot function in the following example code for details)

2.1 TCP programming example

The following is a TCP programming example, which realizes the function of realizing the transmission of large files and displaying the transmission progress.

2.1.1 tcp client

Main functions and implementation methods of the client: specify the server address and port number to be connected from the ui interface, and select the file to be sent. After clicking the send button, call the void QAbstractSocket::connectToHost() function to connect to the server. When connected to the server, it receives the QAbstractSocket::connected() signal, which is associated with the custom slot startTransfer() in advance, and calls the qint64 QTcpSocket::write() to send the file header structure in the custom slot. After each transmission, a byteswriten signal will be received, which will be associated with the user-defined slot updateClientProgress slot. The data will be sent in blocks in the slot function and the progress bar will be updated.

Client ui

Client integrity h file

#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;//Files to send
    qint64 totalBytes;//Total size of sent data
    qint64 bytesWritten;//Size of data sent
    qint64 bytesToWrite;//Remaining data size
    qint64 payloadSize;//Size of data sent each time
    QString fileName;//Save file path
    QByteArray outBlock;//Data buffer, that is, it stores the data blocks to be sent each time
};
#endif // CLIENT_H

Client integrity cpp file

#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);
    //When the connection to the server is successful, a connected signal will be sent to start the file transfer
    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("Open file%1 success!").arg(fileName));
    }
}

void Client::send()
{
    ui->sendButton->setEnabled(false);
    //Initialization sent byte is 0
    bytesWritten=0;
    ui->clientStatusLabel->setText(QString::fromLocal8Bit("Connecting..."));
    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;
    }
    //Get file size
    totalBytes=localFile->size();
    QDataStream sendOut(&outBlock,QIODevice::WriteOnly);
    sendOut.setVersion(QDataStream::Qt_5_6);
    QString currentFileName=fileName.right(fileName.size()-fileName.lastIndexOf('/')-1);
    //Keep the total size information space, file name size information space, and then enter the file name
    sendOut<<qint64(0)<<qint64(0)<<currentFileName;
    //The total size here is the sum of the total size information, file name size information, file name and actual file size
    totalBytes+=outBlock.size();
    //Return to the beginning of outBlock and replace the two qint64(0) spaces with the actual size information
    sendOut.device()->seek(0);
    sendOut<<totalBytes<<qint64(outBlock.size()-sizeof(qint64)*2);
    //The size of the remaining data after sending the file header structure
    bytesToWrite=totalBytes-tcpClient->write(outBlock);
    ui->clientStatusLabel->setText(QString::fromLocal8Bit("Connected"));
    outBlock.resize(0);

}

void Client::updateClientProgress(qint64 numBytes)
{
    //Size of data sent
    bytesWritten+=(int)numBytes;
    //If data has been sent
    if(bytesToWrite>0)
    {
        //Send 64KB of payloadSize data every time. If the remaining data is less than 64K, send the remaining data size
        outBlock=localFile->read(qMin(bytesToWrite,payloadSize));
        //The size of data remaining after sending data once
        bytesToWrite-=(int)tcpClient->write(outBlock);
        //Clear send buffer
        outBlock.resize(0);
    }
    else
    {
        localFile->close();
    }
    //Update progress bar
    ui->clientProgressBar->setMaximum(totalBytes);
    ui->clientProgressBar->setValue(bytesWritten);
    //If sending is complete
    if(bytesWritten==totalBytes)
    {
        ui->clientStatusLabel->setText(QString::fromLocal8Bit("transfer file%1 success").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("Client ready"));
    ui->sendButton->setEnabled(true);
}


void Client::on_openButton_clicked()
{
    ui->clientProgressBar->reset();
    ui->clientStatusLabel->setText(QString::fromLocal8Bit("Status: waiting to open file"));
    openFile();
}

void Client::on_sendButton_clicked()
{
    send();
}

2.1.2 tcp server side

The main function and implementation of the server side: click the "start monitor" button, then call bool QTcpServer:: listen (const QHostAddress &address = QHostAddress:: Any, quint16 port = 0) interface to open the monitor. Associate QTcpServer::newConnection to the custom slot acceptConnection(). In the acceptConnection() slot function, receive the incoming connection request and obtain its socket tcpserverconnection = tcpserver nextPendingConnection(); Then, the readyRead signal is associated to the updateserverprogress () slot. In the updateServerProgress() slot function, first receive the header structure information such as total number, file name, size and file name respectively, then receive the actual file, and then update the progress bar. The main interfaces used are tcpserverconnection - > bytesavailable(), tcpserverconnection - > readall()

Server side ui

Server integrity h file

#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;//Store total size information
    qint64 bytesReceived;//Size of received data
    qint64 fileNameSize;//File name and size information
    QString fileName;//Storage file name
    QFile* localFile;//Local file
    QByteArray inBlock;//Data buffer
};
#endif // SERVER_H

Server integrity cpp file

#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("monitor"));
    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("Receive connection"));
    //Turn off the server and stop listening
    tcpServer.close();
}

void Server::updateServerProgress()
{
    QDataStream in(tcpServerConnection);
    in.setVersion(QDataStream::Qt_5_6);
    //If the received data is less than 16 bytes, save the incoming file header structure
    if(bytesReceived<=sizeof (qint64)*2)
    {
        if((tcpServerConnection->bytesAvailable()>=sizeof(qint64)*2)
                &&(fileNameSize==0))
        {
            //Receive total data size information and file name size information
            in>>totalBytes>>fileNameSize;
            bytesReceived+=sizeof(qint64)*2;
        }
        if((tcpServerConnection->bytesAvailable()>=fileNameSize)&&(fileNameSize!=0))
        {
            //Receive file name and create file
            in>>fileName;
            ui->ServerStatusLabel->setText(QString::fromLocal8Bit("receive files%1...").arg(fileName));
            bytesReceived+=fileNameSize;
            localFile=new QFile(fileName);
            if(!localFile->open(QFile::WriteOnly))
            {
                qDebug()<<"server:open file error!";
                return;
            }
        }
        else
        {
            return;
        }
    }
    //If the received data is less than the total number, write to the file
    if(bytesReceived<totalBytes)
    {
        bytesReceived+=tcpServerConnection->bytesAvailable();
        inBlock=tcpServerConnection->readAll();
        localFile->write(inBlock);
        inBlock.resize(0);
    }
    ui->serverProgressBar->setMaximum(totalBytes);
    ui->serverProgressBar->setValue(bytesReceived);
    //When receiving data is complete
    if(bytesReceived==totalBytes)
    {
        tcpServerConnection->close();
        localFile->close();
        ui->startButton->setEnabled(true);
        ui->ServerStatusLabel->setText(QString::fromLocal8Bit("receive files%1 success").arg(fileName));

    }
}

void Server::displayError(QAbstractSocket::SocketError socketError)
{
    qDebug()<<tcpServerConnection->errorString();
    tcpServerConnection->close();
    ui->serverProgressBar->reset();
    ui->ServerStatusLabel->setText(QString::fromLocal8Bit("Server ready"));
    ui->startButton->setEnabled(true);
}


void Server::on_startButton_clicked()
{
    start();
}

2.1.3 operation effect

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Topics: Qt socket Network Protocol QtCreator