使用QTcpSocket及QTcpServer传输大文件

Qt在实际的使用Tcp通信中发现,发送端与接收端并不是一一对应的,会出现发送多次只相应一次的情况,且发送端速度远超接收端时会引起程序崩溃,小文件不存在这样的问题,可忽略,大文件发送之所以出现,其问题的根本点在于Tcp发送与接收端不一致引起的粘包。
因此可根据实际情况制定协议,使用一问一答的方式进行数据传输,牺牲效率以满足稳定性和安全可靠性。

客户端代码如下

TcpClientPro::TcpClientPro(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
	initClient();
}
TcpClientPro::~TcpClientPro()
{
	qDebug() << "~ClientPro()----------------------------";
	if (m_client->state() == QAbstractSocket::ConnectedState) {
		//如果使用disconnectFromHost()不会重置套接字,isValid还是会为true
		m_client->abort();
	}
}
void TcpClientPro::initClient() {
	//创建client对象
	m_client = new QTcpSocket(this);
	ui.selectBtn->setEnabled(false);
	ui.sendBtn->setEnabled(false);
}
void TcpClientPro::on_connectBtn_clicked() {
	if (m_client->state() == QAbstractSocket::ConnectedState) {
		//如果使用disconnectFromHost()不会重置套接字,isValid还是会为true
		m_client->abort();
	}
	else if (m_client->state() == QAbstractSocket::UnconnectedState) {
		//从界面上读取ip和端口
		const QHostAddress address = QHostAddress(ui.addressEt->text());
		const unsigned short port = ui.portEt->text().toUShort();
		//连接服务器
		m_client->connectToHost(address, port);
	}
	else {
		
	}
	connect(m_client, &QTcpSocket::connected, this, &TcpClientPro::connectedSlot);
	connect(m_client, &QTcpSocket::disconnected, this, &TcpClientPro::disconnectedSlot);
	connect(m_client, &QTcpSocket::readyRead, this, &TcpClientPro::readyReadSlot);
}
void TcpClientPro::on_selectBtn_clicked() {
	QString filePath = QFileDialog::getOpenFileName(this, "open", "../");
	if (!filePath.isEmpty()) {
		m_fileName = "";
		QFileInfo info(filePath);
		m_fileName = info.fileName();
		qDebug() << "m_fileName----------" << m_fileName;
		m_file.setFileName(filePath);
		if (m_file.open(QIODevice::ReadOnly)) {

			FileDate fdata;
			int size = m_file.size();
			if (size == 0) {
				m_file.close();
				return;
			}
			m_lastSize = size % sizeof(fdata.data);
			m_readNum = size / sizeof(fdata.data) + (m_lastSize > 0 ? 1 : 0);
			QByteArray array1 = m_fileName.toStdString().c_str();//
			strncpy(fdata.fileName, array1.data(), sizeof(fdata.fileName));//
			//fdata.FileName = m_fileName;
			fdata.readCnt = m_readNum;
			fdata.lastSize = m_lastSize;
			fdata.type = TransFileInfo;
			qDebug() << "sizeof(fdata)-----------------------" << sizeof(fdata);
			QByteArray array;
			array.resize(sizeof(fdata));
			memcpy(array.data(), &fdata, sizeof(fdata));//把结构体存入数组
			m_client->write(array);
			m_client->waitForBytesWritten();
		}
	}
}
void TcpClientPro::on_sendBtn_clicked() {

}
void TcpClientPro::connectedSlot() {
	ui.connectBtn->setText("Disconnect");
	ui.selectBtn->setEnabled(true);
	ui.addressEt->setEnabled(false);
	ui.portEt->setEnabled(false);
}
void TcpClientPro::disconnectedSlot() {
	ui.connectBtn->setText("Connect");
	ui.addressEt->setEnabled(true);
	ui.portEt->setEnabled(true);
	ui.selectBtn->setEnabled(false);
	ui.sendBtn->setEnabled(false);
}
void TcpClientPro::readyReadSlot() {
	if (m_client->bytesAvailable() <= 0)
		return;
	FileDate fdata;
	QByteArray array;
	array = m_client->readAll();
	memcpy(&fdata, array.data(), sizeof(fdata));//转化到结构体
	if (fdata.state == CorrectState) {
		if (fdata.type == TransFileInfoDone) {
			m_readIndex = 0;
			readFileData();
		}
		else if (fdata.type == TransFileData) {
			readFileData();
		}
		if (m_readIndex == m_readNum) {
			m_file.close();
		}
	}
	else {
		m_file.close();
	}
}
void TcpClientPro::readFileData() {
	QByteArray r_array;
	FileDate r_fdata;
	r_array.resize(sizeof(r_fdata));
	r_fdata.type = TransFileData;
	m_file.read(r_fdata.data, sizeof(r_fdata.data));
	memcpy(r_array.data(), &r_fdata, sizeof(r_fdata));//把结构体存入数组
	m_client->write(r_array);
	m_client->waitForBytesWritten(1000);
	m_readIndex++;
	qDebug() << "TcpClientPro::readFileData----" << m_readIndex;
}

服务端代码如下:


```go
TcpServerPro::TcpServerPro(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
	setWindowTitle("Server");
	iniServer();
}
TcpServerPro::~TcpServerPro()
{
	closeServer();
	if (m_socket != NULL) {
		delete m_socket;
		m_socket = NULL;
	}
}
void TcpServerPro::iniServer() {
	m_fileName = "";
	m_readCnt = 0;
	m_lastSize = 0;
	m_socket = NULL;
	ui.clearBtn->setEnabled(false);
	m_server = new QTcpServer(this);
}
void TcpServerPro::closeServer() {
	m_server->close();
	if (m_socket == NULL) {
		return;
	}
	//断开与客户端的连接
	if (m_socket->state() == QAbstractSocket::ConnectedState) {
		m_socket->disconnectFromHost();
		if (m_socket->state() != QAbstractSocket::UnconnectedState) {
			m_socket->abort();
		}
	}
}
void TcpServerPro::on_listenBtn_clicked() {
	if (m_server->isListening()) {
		closeServer();
		//关闭server后恢复界面状态
		ui.listenBtn->setText("Listen");
		ui.addressEt->setEnabled(true);
		ui.portEt->setEnabled(true);
	}
	else {
		//可以使用 QHostAddress::Any 监听所有地址的对应端口
		const QString address_text = ui.addressEt->text();
		const unsigned short port = ui.portEt->text().toUShort();
		const QHostAddress address = (address_text == "Any")
			? QHostAddress::Any
			: QHostAddress(address_text);
		//开始监听,并判断是否成功
		if (m_server->listen(address, port)) {
			//连接成功就修改界面按钮提示,以及地址栏不可编辑
			ui.listenBtn->setText("Close");
			ui.addressEt->setEnabled(false);
			ui.portEt->setEnabled(false);
		}
		connect(m_server, &QTcpServer::newConnection, this, &TcpServerPro::newConnectionSlot);
	}
}
void TcpServerPro::on_clearBtn_clicked() {
	ui.receiveEt->clear();
}
void TcpServerPro::newConnectionSlot() {
	if (m_server->hasPendingConnections())
	{
		//nextPendingConnection返回下一个挂起的连接作为已连接的QTcpSocket对象
		//套接字是作为服务器的子级创建的,这意味着销毁QTcpServer对象时会自动删除该套接字。
		m_socket = m_server->nextPendingConnection();
		ui.receiveEt->append("connected.........");
		connect(m_socket, &QTcpSocket::readyRead, this, &TcpServerPro::readyReadSlot);
		ui.clearBtn->setEnabled(true);
	}
}
void TcpServerPro::readyReadSlot() {
	if (m_socket->bytesAvailable() <= 0)
		return;
	FileDate fdata;
	QByteArray array;
	//array.resize(sizeof(fdata));
	array = m_socket->readAll();
	memcpy(&fdata, array.data(), sizeof(fdata));//转化到结构体
	if (fdata.type == TransFileInfo) {
		parseFileData(fdata);
	}
	else if (fdata.type == TransFileData) {
		writeFile(fdata);
	}
}
void TcpServerPro::writeFile(FileDate& fdata) {
	m_readIndex++;
	ui.progressBar->setValue(m_readIndex);
	if (m_readIndex == m_readCnt)//最后一个包
	{
		m_file.write(fdata.data, m_lastSize);
		m_readIndex = 0;
		m_file.close();//传输完毕
		qDebug() << "transfer over------------------------";
		return;
	}
	else {
		m_file.write(fdata.data, sizeof(fdata.data));
		QByteArray array1;
		array1.resize(sizeof(fdata));
		FileDate buf;
		buf.state = CorrectState;
		buf.type = TransFileData;
		memcpy(array1.data(), &buf, sizeof(buf));
		qDebug() << "transfer progress-------" << double(1.0*m_readIndex / m_readCnt) * 100<<"%";
		m_socket->write(array1);
		m_socket->waitForBytesWritten(1000);
	}
}
void TcpServerPro::parseFileData(FileDate &fdata) {
	QString dirname = QFileDialog::getExistingDirectory(this, "SelectDirectory", "/");
	QString filename(fdata.fileName);
	//qDebug() << "dirname: " << dirname;
	QString filePath = dirname.append("/").append(filename);
	QFileInfo info(filePath);
	m_fileName = info.fileName();
	m_readCnt = fdata.readCnt;
	m_lastSize = fdata.lastSize;
	qDebug() << "TcpServerPro::parseFileData m_readCnt: " << m_readCnt << " m_lastSize: " << m_lastSize;
	m_file.setFileName(filePath);
	FileDate fdata1;
	if (m_file.open(QIODevice::WriteOnly)) {
		fdata1.type = TransFileInfoDone;
		fdata1.state = CorrectState;
		ui.progressBar->setMaximum(m_readCnt);
		ui.progressBar->setValue(0);
	}
	else {
		fdata1.type = TransFileData;
		fdata1.state = ErrorState;
	}
	m_readIndex = 0;
	QByteArray array1;
	array1.resize(sizeof(fdata1));
	memcpy(array1.data(), &fdata1 , sizeof(fdata1));
	m_socket->write(array1);
	m_socket->waitForBytesWritten();
}

动态显示效果如下:
在这里插入图片描述

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Qt使用QTcpSocket传输文件可以分为两个部分:服务器端和客户端。 服务器端: 1. 创建一个QTcpServer对象并监听客户端连接。 2. 在QTcpServer的newConnection()信号中,获取连接的套接字(QTcpSocket)。 3. 将套接字与文件相关联,以便传输文件。 4. 通过套接字读取文件内容并发送给客户端。 5. 关闭套接字和文件。 客户端: 1. 创建一个QTcpSocket对象并连接到服务器。 2. 发送一个请求,告诉服务器要下载哪个文件。 3. 从套接字读取文件内容并保存到本地文件。 4. 关闭套接字和文件。 下面是一个简单的例子: 服务器端: ```cpp QTcpServer server; server.listen(QHostAddress::Any, 8888); // 监听任意地址的8888端口 connect(&server, &QTcpServer::newConnection, this, [=]() { QTcpSocket *socket = server.nextPendingConnection(); QFile file("path/to/file"); if (file.open(QIODevice::ReadOnly)) { while (!file.atEnd()) { QByteArray buffer = file.read(1024); // 每次读取1024字节 socket->write(buffer); } file.close(); } socket->disconnectFromHost(); }); ``` 客户端: ```cpp QTcpSocket socket; socket.connectToHost(QHostAddress("127.0.0.1"), 8888); // 连接到服务器 if (socket.waitForConnected()) { socket.write("path/to/file"); // 发送请求 QFile file("path/to/save/file"); if (file.open(QIODevice::WriteOnly)) { while (socket.bytesAvailable() > 0) { QByteArray buffer = socket.read(1024); // 每次读取1024字节 file.write(buffer); } file.close(); } socket.disconnectFromHost(); } ``` 需要注意的是,上面的例子只适用于小文件传输。如果要传输文件,可以考虑分成多个数据包传输并在客户端进行组装,或者使用Qt的QDataStream来进行数据流的传输

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值