MyselfQQ是一个基于Qt5框架开发的轻量级即时通讯软件,支持文本、图片、语音、文件等多种消息类型的发送和接收。其中,文件传输是MyselfQQ的一个核心功能,可以实现高效、稳定、安全的文件传输。
本文将介绍MyselfQQ的文件传输功能,包括如何发送和接收文件,以及如何保证传输的安全性和稳定性。同时,本文会提供相关的代码示例,以方便开发者进行参考和实践。
1.1 选择文件
要发送文件,需要先选择要发送的文件。在MyselfQQ中,可以通过打开本地文件夹或者拖拽文件到发送窗口来选择要发送的文件。
打开本地文件夹的方法比较简单,只需要在主界面点击“文件”菜单,选择“打开文件夹”,然后在弹出的文件选择框中选择要发送的文件即可。
如果要通过拖拽文件来选择要发送的文件,可以在主界面直接拖拽文件到发送窗口,或者在本地文件夹中选择要发送的文件,然后拖拽到发送窗口即可。
1.2 发送文件
选择好要发送的文件后,就可以将文件发送给对方了。在MyselfQQ中,发送文件的逻辑可以分为以下几个步骤:
在发送文件之前,需要先创建文件传输对象。文件传输对象包含了发送方和接收方的相关信息,以及要发送的文件的路径、大小等信息。
文件传输对象的定义如下:
struct FileTransferObject
{
QString fileName; // 文件名
QString filePath; // 文件路径
qint64 fileSize; // 文件大小
QString senderName; // 发送方用户名
QString receiverName; // 接收方用户名
QString ip; // 接收方IP地址
qint16 port; // 接收方端口号
bool isAccepted; // 是否被接收
bool isSending; // 是否正在发送
bool isFinished; // 是否发送完成
qint64 sentSize; // 已发送大小
QTcpSocket* socket; // 用于发送数据的TCP连接
};
其中,senderName和receiverName分别表示发送方和接收方的用户名;ip和port表示接收方的IP地址和端口号;isAccepted、isSending和isFinished分别表示文件是否被接收、是否正在发送、是否发送完成;sentSize表示已发送的文件大小;socket表示用于发送数据的TCP连接。
在创建文件传输对象之后,需要获取接收方的IP地址和端口号。这里使用UDP广播来实现,即发送一个UDP广播,让接收方返回自己的IP地址和端口号。
发送UDP广播的代码如下:
void MainWindow::broadcast()
{
QByteArray datagram = "hello";
QHostAddress broadcastAddress = QHostAddress::Broadcast;
quint16 port = 6666;
udpSocket->writeDatagram(datagram, broadcastAddress, port);
}
在发送UDP广播之后,需要监听接收方返回的IP地址和端口号。当接收到回复消息时,就可以获取到接收方的IP地址和端口号了。
void MainWindow::processPendingDatagrams()
{
while (udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
// 处理接收到的UDP数据包
}
}
处理接收到的UDP数据包的具体代码如下:
void MainWindow::processBroadcastDatagram(const QByteArray& datagram, const QHostAddress& sender, quint16 senderPort)
{
if (datagram == "hello")
{
QByteArray response = QString("%1,%2").arg(QHostInfo::localHostName()).arg(tcpServer->serverPort()).toUtf8();
udpSocket->writeDatagram(response, sender, senderPort);
}
else if (datagram.startsWith("IP:"))
{
QString receiverName = datagram.mid(3);
QString ip = sender.toString();
quint16 port = senderPort;
FileTransferObject* obj = findFileTransferObject(receiverName);
if (obj != nullptr)
{
obj->ip = ip;
obj->port = port;
// 开始发送文件
startSendFile(obj);
}
}
}
在处理接收到的UDP数据包时,如果收到的是“hello”消息,就会回复一个包含本机主机名和TCP监听端口号的消息。如果收到的是“IP:xxx”消息,就会获取到接收方的IP地址和端口号,并开始发送文件。
在获取到接收方的IP地址和端口号后,就可以开始发送文件了。文件的发送采用TCP连接来进行,因为TCP连接可以保证传输的稳定性和安全性。
发送文件的关键代码如下:
void MainWindow::startSendFile(FileTransferObject* obj)
{
QTcpSocket* socket = new QTcpSocket(this);
obj->socket = socket;
connect(socket, SIGNAL(connected()), this, SLOT(onSendConnected()));
connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(onBytesWritten(qint64)));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSendError(QAbstractSocket::SocketError)));
socket->connectToHost(obj->ip, obj->port);
obj->isSending = true;
obj->isAccepted = true;
obj->sentSize = 0;
obj->isFinished = false;
}
void MainWindow::onSendConnected()
{
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
if (socket != nullptr)
{
FileTransferObject* obj = findFileTransferObject(socket);
if (obj != nullptr)
{
QFile file(obj->filePath);
if (file.open(QIODevice::ReadOnly))
{
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_9);
out << qint64(0) << qint64(0) << obj->fileName;
qint64 fileSize = file.size();
out << fileSize;
socket->write(block);
obj->isAccepted = true;
obj->fileSize = fileSize;
}
else
{
socket->close();
}
}
}
}
void MainWindow::onBytesWritten(qint64 bytes)
{
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
if (socket != nullptr)
{
FileTransferObject* obj = findFileTransferObject(socket);
if (obj != nullptr)
{
obj->sentSize += bytes;
if (obj->sentSize == obj->fileSize)
{
obj->isFinished = true;
socket->close();
}
else
{
// 继续发送剩余的数据
}
}
}
}
void MainWindow::onSendError(QAbstractSocket::SocketError error)
{
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
if (socket != nullptr)
{
FileTransferObject* obj = findFileTransferObject(socket);
if (obj != nullptr)
{
obj->isSending = false;
obj->isAccepted = false;
emit sendFileError(obj->fileName, obj->senderName, obj->receiverName, socket->errorString());
// 关闭socket
}
}
}
在开始发送文件时,会创建一个QTcpSocket对象,然后连接到接收方的IP地址和端口号。当连接成功后,会发送一个包含文件名和文件大小的消息,让接收方做好接收文件的准备。接着,会按照一定的数据块大小,将文件分块发送到接收方。每发送一个数据块,都会记录已发送的文件大小,以便在下次发送时从上次发送的位置开始继续发送。
在发送文件的过程中,需要不断地检测已发送的文件大小是否等于文件总大小,以判断是否已经发送完毕。如果已经发送完毕,就会将isFinished设置为true,然后关闭TCP连接。如果出现发送错误,就会设置isSending为false,将isAccepted设置为false,并关闭TCP连接。
当MyselfQQ接收到文件传输请求时,会弹出一个文件传输接收窗口,用户可以选择接收或拒绝文件传输。
文件传输接收窗口的代码如下:
void MainWindow::showFileTransferDialog(FileTransferObject* obj)
{
FileTransferDialog* dialog = new FileTransferDialog(obj, this);
connect(dialog, SIGNAL(accepted(FileTransferObject*)), this, SLOT(onFileTransferAccepted(FileTransferObject*)));
connect(dialog, SIGNAL(rejected(FileTransferObject*)), this, SLOT(onFileTransferRejected(FileTransferObject*)));
dialog->show();
}
如果用户选择接收文件,就会开始接收文件。接收文件的过程采用TCP连接来完成,与发送文件的过程类似。
接收文件的关键代码如下:
void MainWindow::startReceiveFile(FileTransferObject* obj)
{
QTcpSocket* socket = new QTcpSocket(this);
obj->socket = socket;
connect(socket, SIGNAL(connected()), this, SLOT(onReceiveConnected()));
connect(socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onReceiveError(QAbstractSocket::SocketError)));
socket->connectToHost(obj->senderIp, obj->senderPort);
obj->isSending = false;
obj->isAccepted = true;
obj->isFinished = false;
}
void MainWindow::onReceiveConnected()
{
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
if (socket != nullptr)
{
FileTransferObject* obj = findFileTransferObject(socket);
if (obj != nullptr)
{
QByteArray block;
QDataStream in(&block, QIODevice::ReadOnly);
in.setVersion(QDataStream::Qt_5_9);
qint64 blockSize = qint64(0);
qint64 fileSize = qint64(0);
QString fileName;
socket->bytesAvailable();
while (socket->bytesAvailable() < sizeof(qint64) * 2 + sizeof(QString))
{
if (!socket->waitForReadyRead(30000))
{
socket->close();
return;
}
}
in >> blockSize;
in >> fileSize;
in >> fileName;
obj->isAccepted = true;
obj->fileSize = fileSize;
obj->fileName = fileName;
obj->sentSize = 0;
}
}
}
void MainWindow::onReadyRead()
{
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
if (socket != nullptr)
{
FileTransferObject* obj = findFileTransferObject(socket);
if (obj != nullptr)
{
if (obj->isFinished)
{
return;
}
if (obj->sentSize == 0)
{
obj->file = new QFile(obj->filePath);
if (!obj->file->open(QIODevice::WriteOnly))
{
socket->close();
return;
}
}
qint64 bytesCount = qint64(0);
QByteArray buffer;
while (socket->bytesAvailable() > 0)
{
buffer = socket->read(qMin(socket->bytesAvailable(), qint64(1024)));
bytesCount += obj->file->write(buffer);
obj->sentSize += bytesCount;
}
if (obj->sentSize == obj->fileSize)
{
obj->isFinished = true;
obj->file->close();
socket->close();
emit receiveFileFinished(obj->fileName, obj->senderName, obj->receiverName);
}
}
}
}
void MainWindow::onReceiveError(QAbstractSocket::SocketError error)
{
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
if (socket != nullptr)
{
FileTransferObject* obj = findFileTransferObject(socket);
if (obj != nullptr)
{
obj->isSending = false;
obj->isAccepted = false;
emit receiveFileError(obj->fileName, obj->senderName, obj->receiverName, socket->errorString());
// 关闭socket
}
}
}
当接收方与发送方建立TCP连接后,接收方会等待发送方发送一个消息,其中包含了文件名和文件大小。接收方会先读取这个消息,然后创建一个与文件名相同的文件,并开始接收发送方发送的数据。每接收一段数据,就会将其写入文件,并记录已接收的文件大小。当已接收的文件大小等于文件总大小时,就认为文件已经接收完成。
在MyselfQQ中,为了保证文件传输的安全和稳定,采取了以下几个措施:
采用TCP连接传输文件,以保证传输的稳定性和安全性。
对文件进行分块传输,每个数据块的大小为1024字节,以减小传输过程中出现中断的风险。
在发送文件时,会记录已发送的文件大小,以便在下次发送时从上次发送的位置开始继续发送。
在接收文件时,会记录已接收的文件大小,以便在下次接收时从上次接收的位置开始继续接收。
在发送文件和接收文件时,会对TCP连接的错误进行处理,以保证传输的可靠性和稳定性。
MyselfQQ的文件传输功能采用TCP连接来实现,支持多种类型的文件传输,如文本、图片、语音和文件等。在文件传输过程中,采取了多种措施来保证传输的安全性和稳定性。开发者可以根据本文提供的代码示例学习和实践,以在自己的应用中实现类似的文件传输功能。
Qt5开发及实例_CH1801.rar