Qt实现TCP文件传输例子

  • (2022.4.4)完整的课设已经挂上 github:iTomxy/TomChat

  • (2018.6.8)收到评论说那个下载工程贵,去看了一下下载页…卧槽要 11 个积分(我当初应该没这么猖狂敢这么开价,毕竟主要是从 Qt 社区抄来的代码,记得好像是设了 3 分来着?)…
    不管怎么样,在此说明:工程的主要代码都在文章里贴出了,文末号称“完整代码”的下载链只是把整个工程打包了上传上去,没多什么东西的啊!
    如果真的需要整个工程,要不就直接联系我让我发给你吧。


参考

第38篇 网络(八)TCP(二)
QT TCP socket通信(二)

效果

  • 发送端
  1. 标签,提示状态信息
  2. 进度条,提示发送进度
  3. 选择按钮,单击弹出选文件对话框
  4. 发送按钮,单击开始发送
  • 接收端
  1. 标签,提示状态信息
  2. 进度条,提示发送进度
  3. 监听按钮,单击开始监听端口
  • 效果图
  1. 工程截图
    project
  2. 运行效果
    result

Notes

  • 发送端取消按钮未实现,忽略…
  • socket 向链接成功发送数据后,会发出bytesWritten(qint64)信号
  • QDataStream 设置版本(setVersion())时,发送端和接收端要设置成同一版本

主要代码

发送端

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QAbstractSocket>
class QByteArray;
class QFile;
class QString;
class QTcpSocket;

namespace Ui {
	class MainWindow;
}

class MainWindow : public QMainWindow
{
	Q_OBJECT

public:
	explicit MainWindow(QWidget *parent = 0);
	~MainWindow();

private slots:
	void start_transfer();
	void continue_transfer(qint64);
	void show_error(QAbstractSocket::SocketError);
	void on_selectBtn_clicked();
	void on_sendBtn_clicked();

private:
	Ui::MainWindow *ui;
	QTcpSocket *send;
	QFile *file;
	QString fileName;
	/* 总数据大小,已发送数据大小,剩余数据大小,每次发送数据块大小 */
	qint64 fileBytes, sentBytes, restBytes, loadBytes;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QByteArray>
#include <QDataStream>
#include <QFileDialog>
#include <QHostAddress>
#include <QIODevice>
#include <QString>
#include <QTcpSocket>

const quint16 PORT = 3333;
const qint64 LOADBYTES = 4 * 1024; // 4 kilo-byte
const int DATA_STREAM_VERSION = QDataStream::Qt_5_8;

MainWindow::MainWindow(QWidget *parent) :
	QMainWindow(parent),
	ui(new Ui::MainWindow)
{
	ui->setupUi(this);
	send = new QTcpSocket(this);
	fileBytes = sentBytes = restBytes = 0;
	loadBytes = LOADBYTES;
	file = Q_NULLPTR;
	ui->sendProg->setValue(0); // 进度条置零
	ui->sendBtn->setEnabled(false); // 禁用发送按钮
	ui->cancelBtn->setEnabled(false);
	/* 连接已建立 -> 开始发数据 */
	connect(send, SIGNAL(connected()),
			this, SLOT(start_transfer()));
	/* 数据已发出 -> 继续发 */
	connect(send, SIGNAL(bytesWritten(qint64)),
			this, SLOT(continue_transfer(qint64)));
	/* socket出错 -> 错误处理 */
	connect(send, SIGNAL(error(QAbstractSocket::SocketError)),
			this, SLOT(show_error(QAbstractSocket::SocketError)));
}

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

/*--- 点击选择按钮 ---*/
void MainWindow::on_selectBtn_clicked()
{
	/* 开文件选择窗选文件,返回带路径文件名 */
	fileName = QFileDialog::getOpenFileName(this);
	if(!fileName.isEmpty())
	{
		ui->stLabel->setText(
			QString("File %1 Opened!").arg(fileName));
		ui->sendBtn->setEnabled(true);
	}
	else
		ui->stLabel->setText(QString("*** FAIL OPENING FILE"));
}

/*--- 点击发送按钮 ---*/
void MainWindow::on_sendBtn_clicked()
{
	/* 发送连接请求 */
	send->connectToHost(QHostAddress::LocalHost, PORT);
	sentBytes = 0;
	ui->sendBtn->setEnabled(false);
	ui->cancelBtn->setEnabled(true);
	ui->stLabel->setText(QString("Linking..."));
}

/*--- 开始传送 ---*/
void MainWindow::start_transfer()
{
	file = new QFile(fileName);
	if(!file->open(QFile::ReadOnly))
	{
		ui->stLabel->setText(QString("*** FILE OPEN ERROR"));
		qDebug() << "*** start_transfer(): File-Open-Error";
		return;
	}

	fileBytes = file->size();
	ui->sendProg->setValue(0);
	ui->stLabel->setText(QString("Connection Established!"));

	QByteArray buf;
	QDataStream out(&buf, QIODevice::WriteOnly);
	out.setVersion(DATA_STREAM_VERSION);

	/* 无路径文件名 */
	QString sfName = fileName.right(fileName.size() -
			fileName.lastIndexOf('/') - 1);
	/* 首部 = 总大小 + 文件名长度 + 文件名 */
	out << qint64(0) << qint64(0) << sfName;
	/* 总大小加上首部的大小 */
	fileBytes += buf.size();
	ui->sendProg->setMaximum(fileBytes);
	/* 重写首部的前两个长度字段 */
	out.device()->seek(0);
	out << fileBytes << (qint64(buf.size()) - 2 * sizeof(qint64));
	/* 发送首部,计算剩余大小 */
	restBytes = fileBytes - send->write(buf);
}

/*--- 继续传输 ---*/
void MainWindow::continue_transfer(qint64 sentSize)
{
	sentBytes += sentSize;
	ui->sendProg->setValue(sentBytes);
	/* 还有数据要发 */
	if(restBytes > 0)
	{
		/* 从文件读数据 */
		QByteArray buf = file->read(qMin(loadBytes, restBytes));
		/* 发送 */
		restBytes -= send->write(buf);
	}
	else
		file->close();
	/* 全部发送完 */
	if(sentBytes == fileBytes)
	{
		send->close(); // 关socket
		fileName.clear(); // 清空文件名
		ui->stLabel->setText(QString("Finish sending!"));
	}
}

/*--- 出错处理 ---*/
void MainWindow::show_error(QAbstractSocket::SocketError)
{
	qDebug() << "*** Socket Error";
	send->close();
	ui->stLabel->setText(QString("*** SOCKET ERROR, RESEND LATER"));
	ui->sendBtn->setEnabled(true);
	ui->sendProg->reset(); // 进度条归零
	fileName.clear();
}

接收端

receivefile.h

#ifndef RECEVIEFILE_H
#define RECEVIEFILE_H

#include <QMainWindow>
#include <QAbstractSocket>
class QFile;
class QString;
class QTcpServer;
class QTcpSocket;

namespace Ui {
	class RecevieFile;
}

class RecevieFile : public QMainWindow
{
	Q_OBJECT

public:
	explicit RecevieFile(QWidget *parent = 0);
	~RecevieFile();

private slots:
	void accept_connect();
	void recevie_file();
	void show_error(QAbstractSocket::SocketError);
	void on_listenBtn_clicked();

private:
	Ui::RecevieFile *ui;
	QTcpServer *server;
	QTcpSocket *receive;
	QString fileName;
	QFile *file;
	/* 已接受数据,总数据,文件名长度 */
	qint64 gotBytes, fileBytes, nameSize;
};

#endif // RECEVIEFILE_H

receivefile.cpp

  • (2022.5.7)@qq_28885527 指出「对于一次性接收的文件不能传输」的问题,并给出修改方案,详见此评论
#include "receviefile.h"
#include "ui_receviefile.h"
#include <iostream>
#include <QDataStream>
#include <QFile>
#include <QHostAddress>
#include <QTcpServer>
#include <QTcpSocket>

const quint16 PORT = 3333;
const int DATA_STREAM_VERSION = QDataStream::Qt_5_8;

RecevieFile::RecevieFile(QWidget *parent) :
	QMainWindow(parent),
	ui(new Ui::RecevieFile)
{
	ui->setupUi(this);
	/* 进度条调零 */
	ui->recvProg->setValue(0);
	/* 启用监听按钮 */
	ui->listenBtn->setEnabled(true);
	fileBytes = gotBytes = nameSize = 0;
	file = Q_NULLPTR;
	receive = Q_NULLPTR;
	server = new QTcpServer(this);
	/* 连接请求 -> 接受连接 */
	connect(server, SIGNAL(newConnection()),
			this, SLOT(accept_connect()));
}

RecevieFile::~RecevieFile()
{
	delete ui;
	delete server;
}

/*--- 点击监听按钮 ---*/
void RecevieFile::on_listenBtn_clicked()
{
	/* 禁用监听按钮 */
	ui->listenBtn->setEnabled(false);
	if(!server->listen(QHostAddress::Any, PORT))
	{
		std::cerr << "*** Listen to Port Failed ***" << std::endl;
		qDebug() << server->errorString();
		close();
		ui->listenBtn->setEnabled(false);
		return;
	}
	ui->stLabel->setText(QString("Listing to Port %1").arg(PORT));
}

/*--- 接受连接请求 ---*/
void RecevieFile::accept_connect()
{
	receive = server->nextPendingConnection();
	/* 有数据到 -> 接受数据 */
	connect(receive, SIGNAL(readyRead()),
			this, SLOT(recevie_file()));
	/* socket出错 -> 出错处理 */
	connect(receive, SIGNAL(error(QAbstractSocket::SocketError)),
			this, SLOT(show_error(QAbstractSocket::SocketError)));
	ui->stLabel->setText(QString("Connection Established!"));
	gotBytes = 0;
	server->close();
}

/*--- 接收文件 ---*/
void RecevieFile::recevie_file()
{
	QDataStream in(receive);
	in.setVersion(DATA_STREAM_VERSION);

	/* 首部未接收/未接收完 */
	if(gotBytes <= 2 * sizeof(qint64))
	{
		if(!nameSize) // 前两个长度字段未接收
		{
			if(receive->bytesAvailable() >= 2 * sizeof(qint64))
			{
				in >> fileBytes >> nameSize;
				gotBytes += 2 * sizeof(qint64);
				ui->recvProg->setMaximum(fileBytes);
				ui->recvProg->setValue(gotBytes);
			}
			else // 数据不足,等下次
			   return;
		}
		else if(receive->bytesAvailable() >= nameSize)
		{
			in >> fileName;
			gotBytes += nameSize;
			ui->recvProg->setValue(gotBytes);
			std::cout << "--- File Name: "
					  << fileName.toStdString() << std::endl;
		}
		else // 数据不足文件名长度,等下次
			return;
	}

	/* 已读文件名、文件未打开 -> 尝试打开文件 */
	if(!fileName.isEmpty() && file == Q_NULLPTR)
	{
		file = new QFile(fileName);
		if(!file->open(QFile::WriteOnly)) // 打开失败
		{
			std::cerr << "*** File Open Failed ***" << std::endl;
			delete file;
			file = Q_NULLPTR;
			return;
		}
		ui->stLabel->setText(QString("Open %1 Successfully!").arg(fileName));
	}
	if(file == Q_NULLPTR) // 文件未打开,不能进行后续操作
		return;

	if(gotBytes < fileBytes) // 文件未接收完
	{
		gotBytes += receive->bytesAvailable();
		ui->recvProg->setValue(gotBytes);
		file->write(receive->readAll());
	}
	if(gotBytes == fileBytes) // 文件接收完
	{
		receive->close(); // 关socket
		file->close(); // 关文件
		delete file;
		ui->stLabel->setText(QString("Finish receiving %1").arg(fileName));
		ui->listenBtn->setEnabled(true);
	}
}

/*--- 出错处理 ---*/
void RecevieFile::show_error(QAbstractSocket::SocketError)
{
	std::cerr << "*** Socket Error ***" << std::endl;
	qDebug() << receive->errorString();
	receive->close(); // 关cocket
	receive = Q_NULLPTR;
	file = Q_NULLPTR;
	fileName.clear(); // 清空文件名
	fileBytes = gotBytes = nameSize = 0;
	ui->recvProg->reset(); // 进度条归零
	ui->listenBtn->setEnabled(true); // 启用监听按钮
	ui->stLabel->setText(QString("*** SOCKET ERROR"));
}

完整工程

Qt实现TCP文件传输例子 ← \leftarrow 这是对应的整个工程,贼贵,还没多出什么实际有用的代码
→ \rightarrow 完整课设 ← \leftarrow 不如下这个,需要的积分更少

评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值