使用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();
}

动态显示效果如下:

你可能感兴趣的:(Qt,qt,tcp/ip)