一、前言
上文讲述了Qt框架下服务器和客户端发信的具体原理,本文讲述Qt框架下如何实现客户端向服务器端发送文件以及关于文件接收不完整的问题。
二、基本功能介绍
首先服务器端绑定IP地址和端口号,客户机端连接对应端口,与上文通信功能一致。文件的传输使用QFile和QFileDialog中的方法进行文件的打开,最后通过write进行传输。
三、具体原理和代码实现
在.pro文件中加入network
QT += core gui network
初始化并分配空间
void Widget::Init()
{
recvTotal = 0;
allTotal = 0;
myServer = new QTcpServer(this);
myFile = new QFile(this);
}
服务器端实现
服务器端IP地址和端口的绑定与上文相同只在数据读取上有所区别
1.ui设计
2.IP绑定
void Widget::on_btn_bind_clicked()
{
QString myAddr = ui->ledit_serv_addr->text();
QString myPort = ui->ledit_serv_port->text();
QString msg;
bool ret = myServer->listen(QHostAddress(myAddr),myPort.toUInt());
if(!ret)
{
msg = "绑定失败";
}
else
{
msg = "绑定成功";
ui->btn_bind->setEnabled(false);
}
ui->textEdit->append(msg);
myServer->setMaxPendingConnections(MAXNUM);
connect(myServer,SIGNAL(newConnection()),this,SLOT(doProcessNewConnection()));
}
3.客户端连入
void Widget::doProcessNewConnection()
{
client = myServer->nextPendingConnection();
QString msg = QString("客户端[%1:%2] 连入!")
.arg(client->peerAddress().toString())
.arg(client->peerPort());
ui->textEdit->append(msg);
//读取内容
connect(client,SIGNAL(readyRead()),this,SLOT(doProcessReadyRead()));
}
4.文件接收
文件接收通过readyRead信号进行槽函数激活,此时分为第一次接受数据和非第一次接收数据,第一次接收数据时需要获取客户端发送过来的文件以及需要上传的大小,同时在对应目录创建新的文件方便后续数据的写入,非第一次接收时通过write进行数据写入,同时更新进度条和接收完毕判定
void Widget::doProcessReadyRead()
{
QByteArray ba = client->readAll();
//第一次接受数据
if(this->recvTotal == 0)
{
this->allTotal = 0;
//获取客户端发送过来的文件及需要上传文件的大小
//eg:filename#totalsize#
QStringList list = QString(ba).split("#");
QString filename = list.at(0);
this->allTotal = QString(list.at(1)).toLongLong();
this->allTotal += ba.length();
this->recvTotal += ba.length();
//打开文件
myFile->setFileName(filename);
bool ret = myFile->open(QIODevice::WriteOnly|QIODevice::Truncate);
if(!ret)
{
this->recvTotal = 0;
QMessageBox::warning(this,"warning","创建文件失败!");
return;
}
QString msg = QString("正在接收文件:%1").arg(filename);
ui->textEdit->append(msg);
ui->progressBar->setRange(0,allTotal);
}
else
{
qint64 len =myFile->write(ba);
this->recvTotal+=len;
}
//更新进度条
ui->progressBar->setValue(this->recvTotal);
//判断是否接收完毕
if(this->recvTotal == this->allTotal)
{
myFile->close();
this->recvTotal = 0;
this->allTotal = 0;
QString msg = "接收文件成功!";
ui->textEdit->append(msg);
}
}
客户端实现
1.ui设计
2.服务器连接
void Widget::on_btn_connect_clicked()
{
QString servIp = ui->ledit_serv_addr->text();
QString servPort = ui->ledit_serv_port->text();
myClient->connectToHost(servIp,servPort.toUInt());
connect(myClient,SIGNAL(connected()),this,SLOT(doProcessConnected()));
}
3.上传文件
由QFileDialog打开文件选择窗口进行文件选择,获取文件的大小进行进度条的显示,同时向服务器发送头,即文件的总大小和文件名,发送后使用waitForByteWritten对进程阻塞,防止数据连续发送造成的文件损坏。当发送完成后进行文件的读取+发送,通过write方法对文件进行写入
void Widget::on_btn_file_clicked()
{
QString filename = QFileDialog::getOpenFileName(this,"上传文件",
"C:/Users/admin/Videos/Captures","video(*.mp4)");
if(filename.isEmpty())
{
return;
}
QString msg = QString("准备发送[%1]文件").arg(filename);
ui->textEdit->append(msg);
myFile->setFileName(filename);
bool ret = myFile->open(QIODevice::ReadOnly|QIODevice::Unbuffered);
if(!ret)
{
return;
}
ui->progressBar->setValue(0);
this->totalSize = 0;
this->sendSize = 0;
//获取文件的大小
this->totalSize = myFile->size();
ui->progressBar->setRange(0,this->totalSize);
msg = QString("文件的总大小为:%1").arg(totalSize);
ui->textEdit->append(msg);
//发送头给服务器 eg:filename#totalsize#
QFileInfo info(filename);
msg = QString("%1#%2#")
.arg(info.fileName())
.arg(this->totalSize);
myClient->write(msg.toUtf8());
myClient->waitForBytesWritten();
//准备边读文件边发送数据
qint64 len;
while(!myFile->atEnd())
{
QByteArray ba = myFile->read(MSG_LEN);
len = myClient->write(ba);
this->sendSize+=len;
ui->progressBar->setValue(this->sendSize);
}
myFile->close();
if(this->totalSize == this->sendSize)
{
msg = QString("上传文件成功!");
}
else
{
msg = QString("上传文件失败[totalsize.%1 sendsize:%2]!")
.arg(this->totalSize).arg(this->sendSize);
}
ui->textEdit->append(msg);
}
四、总结
主要问题在于文件传输的完整性,可以通过waitForBytesWritten进行进程阻塞保证文件完整性,否则会导致文件传输进度卡住和文件受损