QT from entry to soil - TCP/IP network communication (and file transfer)

Posted by ckdoublenecks on Sat, 15 Jan 2022 11:39:23 +0100

**

introduction

**
TCP/IP communication (i.e. SOCKET communication) is the communication established through TCP/IP protocol on the basis of following the four-tier architecture of ISO/OSI model by connecting the Server end of the Server and the Client end of the Client through the network cable. The controller can be set as Server or Client.

For details of TCP/IP protocol: TCP/IP protocol details - Zhihu (zhihu.com)

Generally speaking, TCP/IP communication has two parts:

Client and server
QTcpServer (listening socket) and QTcpSocket (communication socket)

Listening socket, as the name suggests, monitors the status of various communications. Once communication is carried out, the listening socket will start the communication socket for communication

After the client actively connects to the server using the connectToHost function, the server will trigger the slot function newConnectio, take out the QTcpServer (listening socket), take out the relevant contents and assign them to the QTcpSocket (communication socket).
The client sends data to the server and triggers readyRead() for processing. The principle is the same when transmitting data to each other.

What works for both parties:

Once a connection is established, it will trigger connected. In particular, the server triggers newConnectio
The same is true for mutual data transmission. Once received, readyread will be triggered
In the server, a listening socket and a communication socket are required. The listening socket is used to listen whether the client sends a request to the server

This blog has made a preliminary study and attempt, and written a small routine based on window communication and file transfer between client and server.

**

1, Client

**
The code of the client is slightly simpler than that of the server. Generally speaking, using the QTcpSocket class in QT to communicate with the server only requires the following five steps:

(1) Create QTcpSocket socket object

  socket = new QTcpSocket(this);

(2) Use this object to connect to the server

QString ip = ui.lineEdit_ip->text();//Get ip
int port = ui.lineEdit_2->text().toInt();//Get port data
socket->connectToHost(ip, port);

(3) Use the write function to send data to the server

QByteArray data = ui.lineEdit_3->text().toUtf8();//Get the data in the lineEdit control and send it to the server
socket->write(data);

(4) When new data arrives in the socket receiving buffer, a readRead() signal will be sent, so a slot function is added for the signal to read the data

connect(socket, &QTcpSocket::readyRead, this, &QTcpClinet::ReadData);
void QTcpClinet::ReadData()
{
    QByteArray buf = socket->readAll();
    ui.textEdit->append(buf);
}

(5) Disconnect from the server (press F1 for help on the difference between close() and disconnectFromHost())

socket->disconnectFromHost();

Clinet routine: (New qt cpt client project)

ui interface

Local loop ip: 127.0.0.1 can be connected to the local ip (ip circulating inside the computer)

If you want to connect to other ip addresses in the LAN - > run (win+R) + CMD + ipconfig - > IPv4 address to view the local ip address

QTcpClinet.h

#include <QtWidgets/QWidget>
#include "ui_QTcpClinet.h"
#include"QTcpSocket.h"
#pragma execution_character_set("utf-8")
class QTcpClinet : public QWidget
{
    Q_OBJECT

public:
    QTcpClinet(QWidget *parent = Q_NULLPTR);
    ~QTcpClinet();
public slots:
    void on_btn_connect_clicked();
    void ReadData();
    void on_btn_push_clicked();
private:
    Ui::QTcpClinetClass ui;
    QTcpSocket* socket;//Create socket pointer
};

QTcpClinet.cpp

#include "QTcpClinet.h"

QTcpClinet::QTcpClinet(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    socket = new QTcpSocket(this);
}

QTcpClinet::~QTcpClinet()
{
    delete this->socket;//Reclaim memory
}

void QTcpClinet::on_btn_connect_clicked()
{
  if (ui.btn_connect->text()==tr("Connect server"))
  {
    QString ip = ui.lineEdit_ip->text();//Get ip
    int port = ui.lineEdit_2->text().toInt();//Get port data
    //Cancel existing connection
    socket->abort();
    //Connect server
    socket->connectToHost(ip, port);
    bool isconnect = socket->waitForConnected();//Wait until the connection is successful
    //If the connection is successful
    if (isconnect)
    {
        ui.textEdit->append("The connection was successful!!");
        ui.btn_push->setEnabled(true);//Button enable
        //Modify key text
        ui.btn_connect->setText("Disconnect server");
        //Receive buffer (server) information
        connect(socket, &QTcpSocket::readyRead, this, &QTcpClinet::ReadData);
    }
    else
    {
        ui.textEdit->append("The connection falied!!");
    }
  }
  else
  {
      //Disconnect
      socket->disconnectFromHost();
      ui.btn_connect->setText("Connect server");
      ui.btn_push->setEnabled(false);//Turn off send button enable
  }

}

//Receive buffer information function
void QTcpClinet::ReadData()
{
    QByteArray buf = socket->readAll();
    ui.textEdit->append(buf);
}
//Send button event
void QTcpClinet::on_btn_push_clicked()
{
    QByteArray data = ui.lineEdit_3->text().toUtf8();//Get the data in the lineEdit control and send it to the server
    socket->write(data);
    //Determine whether the write is successful
    bool iswrite = socket->waitForBytesWritten();
    if (iswrite)
    {
        //Write successful
    }
    else
    {
        //No write succeeded
    }
}

**

2, Server (need to be running all the time)

**
In addition to using the QTcpSocket class, the server also needs to use the qtcpserver class. Even so, it is only a little more complex than the client. It takes six steps:

(1) Create qtcpserver object

server = new QTcpServer(this);

(2) Listen on a port so that clients can use this port to access the server

server->listen(QHostAddress::Any, 6677);// Listen to all ip and 6677 ports

(3) When the server is accessed by the client, it will send a newConnection() signal, so a slot function is added for the signal, and a QTcpSocket object is used to accept client access

connect(server, &QTcpServer::newConnection, this, &TcpServer::ClientConnect);
void TcpServer::ClientConnect()
{
    //Resolve all customer connections
    while (server->hasPendingConnections())
    {
        //After connecting, obtain the connection information through the socket (QTcpSocket object)
        socket = server->nextPendingConnection();
        QString str = QString("[ip:%1,port:%2]").arg(socket->peerAddress().toString()).arg(socket->peerPort());//Monitor whether the client sends a message
        connect(socket, &QTcpSocket::readyRead, this, &TcpServer::ReadData1);
    }
}

(4) Use the write function of socket to send data to the client

socket->write(data);

(5) When new data arrives in the socket receiving buffer, a readRead() signal will be sent, so a slot function is added for the signal to read the data

//Monitor whether the client sends a message
connect(socket, &QTcpSocket::readyRead, this, &TcpServer::ReadData1);
//Get the information sent by the client to the server
void TcpServer::ReadData1()
{
    QByteArray buf = socket->readAll();//readAll can receive up to 65532 data
    QString str = QString("[ip:%1,port:%2]").arg(socket->peerAddress().toString()).arg(socket->peerPort());
    ui.textEdit_server->append(str +QString(buf));
    //socket->write("ok");// The server returns an OK after receiving the information
}

(6) Cancel listening

server->close();

Server routine: (add a new qt project TcpServer (server))

ui interface

TcpServer.h

#include <QtWidgets/QWidget>
#include"ui_TcpServer.h"
#include"qtcpserver.h"
#include"qtcpsocket.h"

class TcpServer : public QWidget
{
    Q_OBJECT

public:
    TcpServer(QWidget *parent = Q_NULLPTR);
    ~TcpServer();
public slots:
    void on_btn_server_clicked();
    void on_btn_listen_clicked();
private:
    Ui::TcpServerClass ui;
    QTcpServer* server;
    QTcpSocket* socket;//A client corresponds to a socket
    void ClientConnect();
    void ReadData1();
    
};

TcpServer.cpp

#include "TcpServer.h"
#include"qstring.h"
#include"qdebug.h"
#pragma execution_character_set("utf-8")
TcpServer::TcpServer(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    server = new QTcpServer(this);
   //Client connection signal slot
    connect(server, &QTcpServer::newConnection, this, &TcpServer::ClientConnect);
}

TcpServer::~TcpServer()
{
    server->close();
    server->deleteLater();
}

void TcpServer::on_btn_listen_clicked()
{
    if (ui.btn_listen->text()=="Listen")
    {
        //Get the port number from the input box
        int port = ui.lineEdit_port->text().toInt();
        //Listen for all ip addresses on the specified port
        if (!server->listen(QHostAddress::Any, port))
        {
            //If an error occurs, an error message is output
            qDebug() << server->errorString();
            return;
        }
        //Modify key text
        ui.btn_listen->setText("Cancel listening");    
    }
    else
    {
        socket->abort();
        //Cancel listening
        server->close();
        //Modify key text
        ui.btn_listen->setText("Listen");
    }
}

void TcpServer::ClientConnect()
{
    //Resolve all customer connections
    while (server->hasPendingConnections())
    {
        //After connecting, obtain the connection information through socket
        socket = server->nextPendingConnection();
        QString str = QString("[ip:%1,port:%2]").arg(socket->peerAddress().toString()).arg(socket->peerPort());
        //Prompt for successful connection
        ui.textEdit_server->append(str+"Connect to the server");
        //The check box option is the ip address to which the server is connected
        ui.comboBox->addItem(str);
        //Put the socket address into the combobox attribute
        //ui.comboBox->setItemData(ui.comboBox->count()-1, QVariant((int)socket));
        //Monitor whether the client sends a message
        connect(socket, &QTcpSocket::readyRead, this, &TcpServer::ReadData1);
    }
}

//Get the information sent by the client to the server
void TcpServer::ReadData1()
{
    QByteArray buf = socket->readAll();//readAll can receive up to 65532 data
    QString str = QString("[ip:%1,port:%2]").arg(socket->peerAddress().toString()).arg(socket->peerPort());
    ui.textEdit_server->append(str +QString(buf));
}

//The server sends information to the client
void TcpServer::on_btn_server_clicked()
{
  if(ui.comboBox->count()== 0)return;
  //QTcpSocket* skt=  (QTcpSocket*)ui.comboBox->itemData(ui.comboBox->currentIndex()).value<int>();
  socket->write(ui.lineEdit1->text().toUtf8());
}

Note: in write, you need to write an element of type char or an element of type QByteArray

Effect display:

**

3, TCP/IP file transfer

**
The message transmission is realized above, because socket - > readAll(); (readAll receives 65532 data at most), so it is not advisable to use this method for the transmission of large files.

Idea of TCP/IP file transmission:

Client and server connections
The client selects the file and sends the file to the server (the file header is sent, format: file name & size)
The server triggers readyRead, then parses the file frame header (obtains the file name and size), and returns an ok message to the client
The client triggers readyRead, then sends file data, and displays the progress through the progressBar
The server triggers readyRead again to receive the file data and save it (determine whether the file frame header or file data is received through ishead)
Code implementation:

New server project (TcpServer)

TcpServer.h

#pragma once

#include <QtWidgets/QWidget>
#include "ui_TcpServer.h"
#include"qtcpserver.h"
#include"qtcpsocket.h"
#pragma execution_character_set("utf-8")
class TcpServer : public QWidget
{
    Q_OBJECT

public:
    TcpServer(QWidget *parent = Q_NULLPTR);
    void hasConnect();
private:
    Ui::TcpServerClass ui;
    QTcpServer* server;
    QTcpSocket* socket;
    bool ishead;
    QString fileName;
    int fileSize;//Total size of received files
    int recvSize;//The size of the currently received file
    QByteArray filebuf;//Currently received file data
};

TcpServer.cpp

#include "TcpServer.h"
#include"qfile.h"
TcpServer::TcpServer(QWidget *parent)
    : QWidget(parent)
{
    ishead = true;
    ui.setupUi(this);
    server = new QTcpServer(this);
    //Listen to the ip address of port 1122
    server->listen(QHostAddress::Any, 1122);
    //If there is a user connection, trigger the slot function
    connect(server, &QTcpServer::newConnection, this, &TcpServer::hasConnect);
}

void TcpServer::hasConnect()
{
    while (server->hasPendingConnections()>0)//Determine how many people are currently connected
    {
        //Use socket to connect with our clients. One client corresponds to one socket
        socket = server->nextPendingConnection();
        //Output client information on the server interface
        ui.textEdit->append(QString("%1: New user connection").arg(socket->peerPort()));
        //If the client sends a message, the anonymous function is triggered
        connect(socket, &QTcpSocket::readyRead, [=]() {
            QByteArray buf = socket->readAll();
            //Use a flag bit ishead to determine whether it is a header or a data bit
            if (ishead)
            {
                //If it is a header, parse the header (file name, file size)
                QString str = QString(buf);
                ui.textEdit->append(str);
                QStringList strlist = str.split("&");
                fileName = strlist.at(0);//Resolve frame header file name
                fileSize = strlist.at(1).toInt();//Parse frame header file size
                ishead = false;//The next file received is our data
                recvSize = 0;
                filebuf.clear();
                socket->write("ok");
            }
            else
            {
                //Receive and save files according to file name and file size
                filebuf.append(buf);
                recvSize += buf.size();//Current file size + 1 for each file received
                //When the received file size is equal to the total file size, the file data is received
                if (recvSize>=fileSize)
                {
                    //Save file
                    QFile file(ui.lineEdit->text() + fileName);
                    file.open(QIODevice::WriteOnly);
                    file.write(filebuf);
                    file.close();
                    ishead = true;
                }
            }
            });
    }
}

New client project (QTcpClient)

QTcpClient.h

#include <QtWidgets/QWidget>
#include"ui_QTcpClient.h"
#include"qtcpsocket.h"
#pragma execution_character_set("utf-8")
class QTcpClient : public QWidget
{
    Q_OBJECT

public:
    QTcpClient(QWidget *parent = Q_NULLPTR);
public slots:
    void on_btn_connect_clicked();
    void on_btn_choose_clicked();
    void on_btn_open_clicked();
    
private:
    Ui::QTcpClientClass ui;
    QTcpSocket* socket;
};

QTcpClient.cpp

#include "QTcpClient.h"
#include"qfiledialog.h"
#include"qfileinfo.h"
QTcpClient::QTcpClient(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    socket = new QTcpSocket(this);
    
}
void QTcpClient::on_btn_connect_clicked()
{
    QString ip = ui.lineEdit_ip->text();//Get ip
    int port = ui.lineEdit_port->text().toInt();//Get port data
    socket->connectToHost(ip, port);//Connect server
    //Wait for the connection to succeed
    if (socket->waitForConnected())
    {
        ui.textEdit->append("<font color='green'>Successfully connected to the server!</font>");    
        ui.btn_open->setEnabled(true);
        
        //If the server sends information to the client, the anonymous function is triggered
        connect(socket, &QTcpSocket::readyRead, [=]() {
            //Read the information sent by the server (i.e. buffer information)
            QByteArray buf = socket->readAll();
            if (buf=="ok")
            {
                QFile file = (ui.label_path->text());
                if (!file.open(QIODevice::ReadWrite))
                {
                    //fail to read file
                    return;
                }
                qint64 currentlen = 0;//Currently sent size
                qint64 allLength = file.size();//Total file size
                do
                {
                    char data[1024];
                    qint64 msize = file.read(data, 1024);//Put the read file into the other array and return the read size
                    socket->write(data, msize);//Send the read data to the server
                    currentlen += msize;//Get the file size currently sent in real time
                    ui.progressBar->setValue(currentlen *100 / allLength);//Update interface progress bar
                } while (currentlen < allLength);//When the sending file is equal to the file size, the sending is completed and the cycle ends
            }
            });

    }
    else
    {
        ui.textEdit->append("<font color='red'>Failed to connect to the server!</font>");
    }
}
//Select file event
void QTcpClient::on_btn_choose_clicked()
{
    QString path = QFileDialog::getOpenFileName(this, "Open file", "", "(*.*)");
    ui.label_path->setText(path);
}
//Send file event
void QTcpClient::on_btn_open_clicked()
{
    QFileInfo info(ui.label_path->text());
    //Use QFileInfo:: fileName, size to obtain the file name and size format: file name & size
    //The server parses the file name and size in this format
    QString head = QString("%1&%2").arg(info.fileName()).arg(info.size());
    //Send the format to the server toUtf8: QString to QByteArray or char type
    socket->write(head.toUtf8()); 

Effect display:

Topics: C++ Qt server UI TCP/IP