如果直接发 发送的数据永远要比接受的块 需要有时间间隔 此时定时器的作用就显现出来了
发送数据都先发头,要保证服务器发送的头,客户端能接受到
发送数据后不要直接读数据,加一个延迟
这里以##作为分隔符
发送多少读多少,
发送数据的操作
QT网络编程中关于发送文件的操作函数 规则就是从文件中读取多少就发送多少
void ServerWidget::senddata()
{
qint64 len =0;
do
{
//每次读4k的内容
char buf[4*1024] ={0};
len = file.read(buf,sizeof(buf)); //read函数返回值为qint64
//读多少发多少
len =socket->write(buf,len); //统计的发送数据
sendsize+=len;
//累积的文件大小
}while(len>0);
//判断文件发送完毕和总的文件作比较即可
if(sendsize==filesize)
{
//提示用户
ui->textEdit->append("文件已发送完毕");
file.close();
//发送完毕后断开连接 ,以免丢包
//建立好的通讯套接字断开连接
socket->disconnectFromHost();
socket->close();
}
}
这个函数调用前需要有定时器的相关操作
需要先激活定时器 先发送头 间隔20ms发送
void ServerWidget::on_butsend_clicked()
{
//发送文件头信息
QString head = QString("%1##%2").arg(filename).arg(filesize);
qint64 len = socket->write(head.toUtf8()); // write函数返回值为qint64
if(len>0)
{
// qDebug()<<"发送成功";
timer->start(20);
}else
{
qDebug()<<"发送信息失败";
file.close();
ui->butselect->setEnabled(1);
ui->butselect->setEnabled(0);
}
}
在选择按钮的函数中调用connect()关联信号和槽
void ServerWidget::on_butselect_clicked()
{
// QString getOpenFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter
QString path = QFileDialog::getOpenFileName(this,"打开文件","../");
if(path.isEmpty()==false)
{
//路径不为空的话就打开该文件
//每次选择文件的时候将文件名和大小清空
filename.clear();
filesize =0;
QFileInfo info(path);
//QFileInfo中有文件信息的成员函数
// file.open();
filename = info.fileName(); //获取文件名字
filesize = info.size();//获取文件大小
//用变量来标记文件发送了多少 防止丢包
sendsize =0 ;
//指定文件名字,只读方式打开文件按
file.setFileName(path);
if(!file.open(QIODevice::ReadOnly))
{
qDebug()<<"只读方式打开文件失败"<<'\n';
}
//提示用户打开文件的路径
ui->textEdit->append(path);
ui->butselect->setEnabled(false);
ui->butselect->setEnabled(true);
}else
{
qDebug()<<"选择文件无效"<<'\n';
}
connect(timer,&QTimer::timeout,[=](){
timer->stop();
senddata();
});
}
客户端:
#include "clientwidget.h"
#include "ui_clientwidget.h"
#include <QMessageBox>
#include <QHostAddress>
Clientwidget::Clientwidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Clientwidget)
{
ui->setupUi(this);
setWindowTitle("客户端");
//ui->progressBar->setValue(0);//设置进度条的值
isstart = true;
csocket = new QTcpSocket(this);
connect(csocket,&QTcpSocket::readyRead,[=](){
QByteArray buf = csocket->readAll();
if(true==isstart)
{
isstart = false;
//初始化
filename = QString(buf).section("##",0,0);
filesize = QString(buf).section("##",1,1).toInt();
recivesize =0;
//打开文件
file.setFileName(filename);
bool isok = file.open(QIODevice::WriteOnly);
if(false ==isok)
{
qDebug()<<"write only errno 29";
}
QString str = QString("接受的文件:[%1:%2kb]").arg(filename).arg(filesize/1024);
QMessageBox::information(this,"文件信息",str);
ui->progressBar->setMinimum(0);
ui->progressBar->setMaximum(100);
ui->progressBar->setValue(0);
}
else
{
qint64 len = file.write(buf);
if(len>0)
{
recivesize+=len;
QString str = QString::number(recivesize);
csocket->write(str.toUtf8().data());
//qDebug()<<len;
}
ui->progressBar->setValue(recivesize/1024);
if(recivesize==filesize)
{
csocket->write("file done");
QMessageBox::information(this,"提示","文件接受完成");
file.close();
csocket->disconnectFromHost();
csocket->close();
return ;
}
}
});
/*
String::section() 是 Qt 中 QString 类的一个成员函数,用于从字符串中提取指定范围内的子字符串。它可以根据分隔符或索引位置来提取子字符串。
QString::section() 函数签名
QString section(QChar sep, int startIndex, int endIndex, QString::SectionFlags flags = KeepEmptyParts) const;
参数说明
sep:分隔符,用于分割字符串。
startIndex:起始索引,表示从第几个分隔符开始提取。
endIndex:结束索引,表示到第几个分隔符结束提取。
flags:标志位,控制如何处理空的部分,默认为 KeepEmptyParts。
返回值
返回一个 QString,包含了从 startIndex 到 endIndex 之间的子字符串。
*/
}
Clientwidget::~Clientwidget()
{
delete ui;
}
void Clientwidget::on_butconnect_clicked()
{
QString ip = ui->ipedit->text();
quint64 port = ui->portedit->text().toInt();
csocket->connectToHost(QHostAddress(ip),port);
}