先上实现结果
一、概述
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