QT5+TCP/IP多线程传输图片

博客介绍了QT中TCP/IP通信的设计,主要使用QTCPServer和QTCPSocket类,功能分服务器端和客户端,服务器接收图片,客户端发送。阐述了多线程设计的两种方式,还给出了UI设计、服务器端和客户端的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先上实现结果

一、概述

        QT中设计TCP/IP通信主要使用QTCPServer和QTCPSocket两个类,功能分为服务器端和客户端,服务器端负责接收图片,客户端发送图片。多线程设计主要有两种方法,一种是通过添加继承于QThread的类,并在此类中实现run()函数为主体业务逻辑。另一种是继承自Qobject的类中实现业务逻辑,实例化以后添加到子线程(使用movetothread()方法,子线程需要在主线程中独立创建)。线程之间通信最好使用信号与槽机制,或者使用指针传递地址,否则容易报出各种错误。

 

二、多线程设计两种方式

1.简易版本(可以实现的逻辑有限)

mainwindow.cpp

 m_ser = new QTcpServer(this);//指定父对象在父对象析构时自动析构
 m_sock = new QTcpSocket(this);
...
connect(m_ser,&QTcpServer::newConnection,this,[=]()
    {
        //得到套接字对象
        m_sock = m_ser->nextPendingConnection();
        //实例化子线程对象
        recvFile* subThread = new recvFile(m_sock);
        subThread->start();//开启子线程
        
    }

recvfile.cpp


recvFile::recvFile(QTcpSocket* tcp,QObject *parent) : QThread(parent)
{
    tcpSocket = tcp;
}



void recvFile::run()
{
    //实现接收文件逻辑
}

 

2.全面版本

mainwindow.cpp

/*多线程相关对象的实例化*/
    //创建线程对象
    QThread *m_thread = new QThread;
    //创建任务对象
    SendFile* worker = new SendFile;
    //任务对象移动到子线程中
    worker->moveToThread(m_thread);//任务对象会在子线程中执行

 sendfile.cpp

SendFile::SendFile(QObject *parent) : QObject(parent)
{

    tcpSocket = new QTcpSocket;
   ...

}

三、代码实现

1.UI设计

服务器

客户端

 

2.服务器端代码实现

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include<recvfile.h>
#include<QMessageBox>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui_Init();//初始化ui
    m_ser = new QTcpServer(this);//指定父对象在父对象析构时自动析构
    m_sock = new QTcpSocket(this);

    qDebug()<<"主线程地址为"<<QThread::currentThread();


    //建立连接以后
    connect(m_ser,&QTcpServer::newConnection,this,[=]()
    {
        //得到套接字对象
        m_sock = m_ser->nextPendingConnection();
        //实例化子线程对象
        recvFile* subThread = new recvFile(m_sock);
        subThread->start();//开启子线程
        ui->startLisenBt->setEnabled(false);
        ui->closeListenBt->setDisabled(false);
        //子线程中接收到的头文件信息显示到接收区
       connect(subThread,&recvFile::headReceived,this,[=](QString buf)
       {
           ui->recvEdit->append(buf);
       });

       //接收文件过程中更新进度条
       connect(subThread,&recvFile::receivingPercent,ui->progressBar,&QProgressBar::setValue);
       //接收完毕以后在主线程进行显示
       connect(subThread,&recvFile::fileReceived,this,[=](QString path)
       {
           ui->statusLabel->setText("文件接收完毕");
           show_picture(path);
       });
    }
            );




    /*
    //当连接断开时
    connect(m_sock,&QTcpSocket::disconnected,this,[=]()
    {
        m_sock->close();
        m_sock->deleteLater();
        m_status->setPixmap(QPixmap(":/red.jpeg").scaled(20,20));
    });
    */

}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow:: ui_Init()
{
    ui->PortEdit->setText("30201");
    setWindowTitle("服务器");
    //监听按钮初始化
    ui->closeListenBt->setDisabled(true);
    //进度条初始化
    ui->progressBar->setRange(0,100);
    ui->progressBar->setValue(0);
    //状态栏初始化
    m_status = new QLabel;
    m_status->setPixmap(QPixmap(":/red.jpeg").scaled(20,20));
    ui->statusbar->addWidget(new QLabel("连接状态"));
    ui->statusbar->addWidget(m_status);
}

//开启监听
void MainWindow::on_startLisenBt_clicked()
{
    unsigned short port = ui->PortEdit->text().toUInt();
    m_ser->listen(QHostAddress::Any,port);//开启监听
    ui->startLisenBt->setDisabled(true);
    //如果已经开启监听
    if(m_ser->isListening() == true)
    {

        ui->statusLabel->setText("已经开启监听");
        ui->closeListenBt->setEnabled(true);
        ui->startLisenBt->setDisabled(true);
        m_status->setPixmap(QPixmap(":/green.jpeg").scaled(20,20));
    }
}




void MainWindow::on_sendTextBt_clicked()
{
    QString msg = ui->SendEdit->toPlainText();
    m_sock->write(msg.toUtf8());
}


/*打开指定路径照片并显示*/
void MainWindow::show_picture(QString &filepath)
{
    if(image.load(filepath))//当图像文件导入以后
    {
        //显示图像
        ui->picShow_Label->setPixmap(QPixmap::fromImage(image).scaled(ui->picShow_Label->size()));
    }
}

void MainWindow::on_closeListenBt_clicked()
{

    m_ser->close();
    if(!m_ser->isListening())
    {
        ui->statusLabel->setText("服务器已关闭");
        ui->startLisenBt->setEnabled(true);
        ui->closeListenBt->setDisabled(true);
    }
    else{
           QMessageBox::critical(this,"错误","服务器关闭失败");
    }
}

 mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include<QTcpServer>
#include<QHostInfo>
#include <QTcpSocket>
#include<QLabel>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_startLisenBt_clicked();

    void on_sendTextBt_clicked();

    void on_closeListenBt_clicked();

private:
    Ui::MainWindow *ui;
    QTcpServer* m_ser;
    QTcpSocket* m_sock;
    QImage image;          //接收并显示的图片对象
    QLabel* m_status;
    void  ui_Init();
    void show_picture(QString &filepath);
};
#endif // MAINWINDOW_H

recvfile.cpp

#include "recvfile.h"
#include<QMessageBox>
recvFile::recvFile(QTcpSocket* tcp,QObject *parent) : QThread(parent)
{
    tcpSocket = tcp;


}

void recvFile::run()
{
    qDebug()<<"子线程地址为"<<QThread::currentThread();
    //初始化文件接收数据
    fileName = "";
    fileSize = 0;
    recvSize = 0;
    isFile = false;
    connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readyRead_Slot()));

}
void recvFile::readyRead_Slot()
{
    QByteArray buf = tcpSocket->readAll();//缓冲区
    if(isFile==false)
    {
        /*先接收文件头*/
        isFile = true;//下一轮接收时就接收文件本身
        //文件名字
        fileName = QString(buf).section("#", 1, 1);
        //文件大小
        fileSize = QString(buf).section("#", 2, 2).toInt();

        QString head ="文件名" + fileName + "\n" + "文件大小" +QString(buf).section("#", 2, 2);
        //文件头信息接收到以后发送到主线程进行显示
        emit headReceived(head);

        recvSize = 0;
        file.setFileName(fileName);

        if(false == file.open(QIODevice::WriteOnly))
        {
            qDebug() <<"文件打开失败";
            //初始化数据
            fileName = "";
            fileSize = 0;
            recvSize = 0;
            isFile = false;
            //QMessageBox::warning(this, "警告", "创建文件失败");
            return;
         }

    }

    else//接收文件本身
    {
        //ui->receiveEdit->append(QString("正在接收文件%1").arg(fileName));
        qint64 len = file.write(buf);
        //qDebug() <<"正在接收文件";
        recvSize += len;
        percent = (100 * recvSize ) / fileSize;
        emit receivingPercent(percent);
        //qDebug() <<"接收的百分比"<<percent;
    }
    if(recvSize == fileSize)//如果接收数据长度和发送数据长度相等做接收后处理
    {
        QFileInfo info;

        //qDebug() <<"接收完毕";
        file.close();
        info = QFileInfo(file);
        imgpath = info.filePath();
        emit fileReceived(imgpath);
        isFile = false;
    }

}

 recvfile.h

#ifndef RECVFILE_H
#define RECVFILE_H

#include <QObject>
#include<QThread>
#include<QTcpSocket>
#include<QFile>
#include<QImage>
#include<QFileInfo>
class recvFile : public QThread
{
    Q_OBJECT
public:
    explicit recvFile(QTcpSocket* tcp,QObject *parent = nullptr);
private:
    QTcpSocket *tcpSocket;
    QFile file;             //文件对象
    QString fileName;       //文件名字
    qint64 fileSize;        //文件大小
    qint64 recvSize;        //已接收文件的大小
    bool  isFile;           //接收文件标志
    bool  alreadyReceive;  //接收完成标志
    QImage image;          //接收并显示的图片对象
    QString imgpath;       //接收到图片的路径
    int percent;         //接收的比例
protected:
    void run() override;
private slots:
    void readyRead_Slot();//接收文件数据的槽函数
signals:
    void headReceived(QString head);//文件头信息接收到以后发送到主线程的接收区显示
    void fileReceived(QString path);//文件完全接收以后发送文件路径到主线程显示
    void receivingPercent(int percent);//接收过程中发送接收文件的百分比
};

#endif // RECVFILE_H

3.客户端代码实现

 mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<sendfile.h>
#include<QThread>
#include<QMessageBox>
//多线程TCP通信的客户端
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    qDebug()<<"主线程地址为"<<QThread::currentThread();
    ui->disconnectBt->setDisabled(true);//初始不能断开连接
    setWindowTitle("客户端");
    //ip,端口号初始化
    ui->PortEdit->setText("30201");
    getIPInfo();//IP、端口号填充
    //进度条初始化
    ui->progressBar->setRange(0,100);
    ui->progressBar->setValue(0);
    connect_FileThread();//子线程初始化和槽函数连接
    tab1 = new QTabWidget(this);
    QWidget *tcp_tab = new QWidget;
    QWidget *serial_tab = new QWidget;
    tab1->addTab(tcp_tab,"tcp");
    tab1->addTab(serial_tab,"ser");
    //状态栏初始化
    m_status = new QLabel;
    m_status->setPixmap(QPixmap(":/red.jpeg").scaled(20,20));
    ui->statusbar->addWidget(new QLabel("连接状态"));
    ui->statusbar->addWidget(m_status);
}

MainWindow::~MainWindow()
{
    delete ui;
}


//发送文件线程连接
void MainWindow::connect_FileThread()
{
    /*多线程相关对象的实例化*/
    //创建线程对象
    QThread *m_thread = new QThread;
    //创建任务对象
    SendFile* worker = new SendFile;
    //任务对象移动到子线程中
    worker->moveToThread(m_thread);//任务对象会在子线程中执行

    //主窗口发送开启连接的信号,接收信号的对象是worker,子线程中SendFile类的connectServer进行工作
    connect(this,&MainWindow::startConnect,worker,&SendFile::connectServer);

    //主窗口发送关闭连接信号
     connect(this,&MainWindow::closeConnect,worker,&SendFile::closeConnect);

    //子线程中完成连接发送信号,主线程接收到以后弹出messagebox
    connect(worker,&SendFile::connected_ok,this,[=]()
    {
        //连接以后的状态转换
        ui->statusLabel->setText("已经连接");
        //QMessageBox::information(this,"连接服务器","已成功连接");
        m_status->setPixmap(QPixmap(":/green.jpeg").scaled(20,20));
        ui->disconnectBt->setEnabled(true);
        ui->connectBt->setDisabled(true);

    });
    //子线程中检测到断开连接则释放资源(销毁各种对象)
    connect(worker,&SendFile::tcp_over,this,[=]()
    {
        //ui状态转换
        m_status->setPixmap(QPixmap(":/red.jpeg").scaled(20,20));
        ui->disconnectBt->setEnabled(false);
        ui->connectBt->setDisabled(false);
        //资源释放
        //m_thread->quit();
        //m_thread->wait();
        //worker->deleteLater();
        //m_thread->deleteLater();

    });
    //在子线程中发送文本
    connect(this,&MainWindow::sendText,worker,&SendFile::sendText);

    //子线程接收到文本,在主线程中更新窗口
    connect(worker,&SendFile::recv_txt,this,[=](QByteArray buf)
    {
        ui->recvEdit->append("服务器发送:" + buf);
    });

    //子线程中发送文件
    connect(this,&MainWindow::sendFile_Path,worker,&SendFile::sendFile);
    //更新发送速度
    //connect(worker,&SendFile::send_Speed,this,[=](double speed)
    //{
      //  ui->speedLabel->setText(tr("发送速度%1 MB/S").arg(speed,0,'f',2));
    //});

    //更新进度条
    connect(worker,&SendFile::curPercent,ui->progressBar,&QProgressBar::setValue);

    //开启子线程
    m_thread->start();
}


void MainWindow::on_sendTextBt_clicked()
{
    QString msg = ui->SendEdit->toPlainText();//从UI中获取发送的信息
    emit sendText(msg);//给sendfile类发送信号
}


void MainWindow::getIPInfo()
{
    QString localHostName = QHostInfo::localHostName();
    QHostInfo hostinfo = QHostInfo::fromName(localHostName);
    QList<QHostAddress> listAddress = hostinfo.addresses();
    if(!listAddress.isEmpty() )
    {
        ui->ipEdit->setText(listAddress.at(0).toString());
    }


}


void MainWindow::on_connectBt_clicked()
{
    QString ip = ui->ipEdit->text();
    unsigned short port = ui->PortEdit->text().toUInt();
    emit startConnect(port,ip);
}

void MainWindow::on_disconnectBt_clicked()
{

    ui->disconnectBt->setEnabled(false);//断开连接按钮不可用
    ui->connectBt->setDisabled(false);//连接按钮可用
    emit closeConnect();//发送断开连接信号

}

void MainWindow::on_selectFileBt_clicked()
{
    QString path = QFileDialog::getOpenFileName();
    ui->filePathEdit->setText(path);
    //获取文件信息
    QFileInfo info(path);
    fileSize = info.size();      //文件大小
    fileName = info.fileName();//文件名

    qDebug()<<"fileSize:"<<fileSize<<endl;
    ui->statusLabel->setText(tr("打开 %1 文件成功!").arg(fileName));

    if(image.load(path))//当图像文件导入以后
    {
        //显示图像
        ui->picShow_Label->setPixmap(QPixmap::fromImage(image).scaled(ui->picShow_Label->size()));
    }

}

void MainWindow::on_sendFileBt_clicked()
{
    emit sendFile_Path(ui->filePathEdit->text());
}

mainwidow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include<QHostInfo>
#include <QTcpSocket>
#include<QLabel>
#include<QFile>
#include<QFileDialog>
#include<QFileInfo>
#include<QImage>
#include<QTabWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:

    void on_sendTextBt_clicked();

    void on_connectBt_clicked();

    void on_disconnectBt_clicked();

    void on_selectFileBt_clicked();

    void on_sendFileBt_clicked();

private:
    Ui::MainWindow *ui;
    QTabWidget *tab1;
    QImage image;//图像文件
    QString fileName;//图片名
    qint64 fileSize;//文件大小
    QTcpSocket* m_sock;
    void getIPInfo();
    QLabel* m_status;
    void connect_FileThread();
signals:
    void  startConnect(unsigned short port,QString ip);
    void  closeConnect();//断开连接信号
    void  sendText(QString data);
    void  sendFile_Path(QString path);
};
#endif // MAINWINDOW_H

sendfile.cpp

#include "sendfile.h"
#include<QHostAddress>
#include<QFileInfo>
#include<QElapsedTimer>
SendFile::SendFile(QObject *parent) : QObject(parent)
{

    tcpSocket = new QTcpSocket;
    //当有文本数据接收到时发射recv_txt信号给主窗口
    connect(tcpSocket,&QTcpSocket::readyRead,this,[=]()
    {
        QByteArray buf = tcpSocket->readAll();
        emit recv_txt(buf);
    });
    //断开连接发送tcp_over
    connect(tcpSocket,&QTcpSocket::disconnected,this,[=]()
    {
       tcpSocket->close();
       //tcpSocket->deleteLater();
       emit tcp_over();
    });

}

void SendFile::connectServer(unsigned short port, QString ip)
{
    qDebug()<<"子线程地址为"<<QThread::currentThread();
    //tcpSocket = new QTcpSocket;
    tcpSocket->connectToHost(QHostAddress(ip),port);//连接操作
    //已经连接发送connnected_ok
    connect(tcpSocket,&QTcpSocket::connected,this,&SendFile::connected_ok);

}

void SendFile::closeConnect()
{
    tcpSocket->disconnectFromHost();
    tcpSocket->close();
}

void SendFile::sendFile(QString path)
{
    QElapsedTimer timer;//定时器记录程序执行使用时间
    QFile file(path);
    QFileInfo info(path);
    qint64 fileSize = info.size();
    QString fileName = info.fileName();
    file.open(QFile::ReadOnly);
    timer.start();
    while (!file.atEnd())
    {
        if(num == 0)
        {
            QString head = QString("head#%1#%2").arg(fileName).arg(fileSize);
            tcpSocket->write(head.toUtf8().data());
            tcpSocket->waitForBytesWritten(); //等待数据发送完毕
        }
        QByteArray line = file.readLine();
        num+=line.size();
        //qDebug()<<"已发送文件大小"<<num;
        //sendSpeed = num  / ( milsec * 1024 ) ;//发送速度
        //qDebug()<<"发送速度"<<sendSpeed;
        //emit send_Speed(sendSpeed);//向主线程发送速度
        qint64 percent = (num * 100 /fileSize);
        emit curPercent(percent);
        tcpSocket->write(line);
    }
    //文件发送完成以后的善后工作
    num = 0;
    int milsec = timer.elapsed();

    qDebug()<<"发送消耗时间"<<milsec<<"毫秒";
    file.close();
}

void SendFile::sendText(QString data)
{
    tcpSocket->write(data.toUtf8());
}


sendfile.h

#ifndef SENDFILE_H
#define SENDFILE_H

#include <QObject>
#include<QTcpSocket>
#include<QFile>
#include<QFileDialog>
#include<QThread>
class SendFile : public QObject
{
    Q_OBJECT
public:
    explicit SendFile(QObject *parent = nullptr);
    //连接服务器
    void connectServer(unsigned short port,QString ip);
    //与服务器断开连接
    void closeConnect();
    //发送文件
    void sendFile(QString path);
    //发送文本
    void sendText(QString data);
private:
   QTcpSocket* tcpSocket ;
   qint64 num=0;
   qint64 sendSpeed = 0;
signals:
    void  connected_ok();
    void  tcp_over();
    void  recv_txt(QByteArray buf);
    void  curPercent(int percent);
    void  send_Speed(qint64 speed);

};

#endif // SENDFILE_H

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值