您的位置:首页 > 理论基础 > 计算机网络

QTCpSocket文件传输

2017-06-06 13:09 435 查看
UDP由于不用建立连接,所以常用于聊天程序(点对点、群聊天等);而TCP由于其建立连接,具有可靠性强、能够保证不丢包,所以经常用于大文件的传输。但是由于TCP粘包,所以在使用TCP进行文件传输时,需要进行粘包问题的考虑。关于TCP/UDP用于聊天程序的应用可以参考:QTcpServer、QTcpSocket、QUdpSocket在聊天程序上的应用 。QTcpSocket、QTcpServer、QFile、QFileInfo这些类是Qt中用于TCP文件传输的一些主要的类。其继承派生关系如下:



文件传输的图解分析如下:



首先我们还是要创建在服务器端用于监听的QTcpServer对象,在客户端创建一个用于通信的QTcpSocket对象,然后请求连接,当连接成功以后就可以选择文件给客户端进行发送了:

发送前先进行文件信息的读取,获取文件信息与以只读方式打开文件,是在选择按钮的槽函数中完成,获取文件大小与文件名,用以发送给和客户端进行客户端文件的创建。

当文件打开、文件信息获取完毕后,Send()的槽函数就完成。接着就可以点击发送按钮,进入发送按钮的槽函数中进行发送,发送分为两部分:头部信息和文件内容数据。头部信息是在发送文件之前,告诉客户段先创建一个文件同名并打开,由于头部信息与文件内容可能会粘包,所以在头部信息发送后,应该暂停等待头部信息接收完成后进行文件内容的发送(可以指定定时器,定时器到时则发送;也可以在客户端接收头部信息完成后回射,服务器来判断回射信息)。我们以回射信息的方式进行粘包处理。

另外,由于头部信息需要发送文件名字、大小等信息。客户端要识别发送过来的QByteArray类型数据,必须先转换成QString,而转换成字符串的数据需要分解为文件名与文件大小,就需要在发送时以一定的格式发送(类似于自定义协议),解析时也按该方式进行解析(比如:在每单个信息之间加上##进行分割,拆包时也同样检测##进行字符串的分割)。

具体测试实现:

/*serverwidget.h*/
#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H

#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QFile>
#include <QTimer>

#define BUF_SIZE 1024*4

namespace Ui {
class ServerWidget;
}

class ServerWidget : public QWidget
{
Q_OBJECT

public:
explicit ServerWidget(QWidget *parent = 0);
~ServerWidget();
void sendData();    //发送文件数据
private slots:
void on_buttonChooseFile_clicked();

void on_butttonSendFile_clicked();

private:
Ui::ServerWidget *ui;
QTcpServer * tcpServer; //监听
QTcpSocket * tcpSocket; //通信
QFile file;             //文件对象
QString fileName;       //文件名字
qint64 fileSize;        //文件大小
qint64 sendSize;        //已经发送大小
QTimer timer;           //定时器
};

#endif // SERVERWIDGET_H


/*serverwidget.cpp*/
#include "serverwidget.h"
#include "ui_serverwidget.h"
#include <QFileDialog>
#include <QFileInfo>
#include <QByteArray>
#include <QMessageBox>

ServerWidget::ServerWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ServerWidget)
{
ui->setupUi(this);
//创建对象指定父对象
tcpServer = new QTcpServer(this);
//绑定监听
tcpServer->listen(QHostAddress::Any,8888);
setWindowTitle("服务器端口:8888");

//两个按钮最开始都不能按
ui->buttonChooseFile->setEnabled(false);
ui->butttonSendFile->setEnabled(false);

//如果客户端和服务器成功连接
//tcpServer会自动触发newConnection()信号
connect(tcpServer,&QTcpServer::newConnection,
[=](){
//获取通信套接字
tcpSocket = tcpServer->nextPendingConnection();
//获取对方IP、port
QString ip = tcpSocket->peerAddress().toString();
quint16 port = tcpSocket->peerPort();

QString str = QString("[%1:%2]:成功连接").arg(ip).arg(port);
ui->textEdit->append(str);

//成功连接后可以选择
ui->buttonChooseFile->setEnabled(true);

connect(tcpSocket, QTcpSocket::readyRead,
[=](){
QByteArray buf = tcpSocket->readAll();
//采用回射信息进行粘包处理
if("FileHead recv" == QString(buf)){
//ui->textEdit->append("文件头部接收成功,开始发送文件...");
sendData();
}
else if("file write done" == QString(buf)){
//服务器发送的快,而客户端接收的慢,所以要等客户端接收完毕后才能断开连接,以免丢包
QMessageBox::information(this,"完成","对端接收完成");
//ui->textEdit->append("文件发送且接收完成");
file.close();
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
}
);
}
);
}

ServerWidget::~ServerWidget()
{
delete ui;
}

//选择文件
void ServerWidget::on_buttonChooseFile_clicked()
{
QString filePath = QFileDialog::getOpenFileName(this,"open","../");
if(false == filePath.isEmpty()){    //路径有效
fileName.clear();
fileSize = 0;
//获取文件信息:名字、大小
QFileInfo info(filePath);
fileName = info.fileName();
fileSize = info.size();
sendSize = 0;   //已经发送文件大小

//以只读方式打开文件
file.setFileName(filePath);
if(false == file.open(QIODevice::ReadOnly)){
ui->textEdit->append("只读方式打开文件失败");
}
//提示已经打开的文件路径
ui->textEdit->append(filePath);

//可以发送
ui->buttonChooseFile->setEnabled(false);  //只能选择一次
ui->butttonSendFile->setEnabled(true);
}
else{
ui->textEdit->append("选择文件路径无效:SERVER80");
}
}

void ServerWidget::on_butttonSendFile_clicked()
{
//发送按钮已经点击,发送过程中不能再点击,恢复按钮初始化
ui->buttonChooseFile->setEnabled(false);
ui->butttonSendFile->setEnabled(false);
//先发送文件头信息:文件名##大小
//构造头部信息
QString head = QString("%1##%2").arg(fileName).arg(fileSize);
//发送头部信息
qint64 len = tcpSocket->write(head.toUtf8());

if(len < 0){
ui->textEdit->append("文件头部信息发送失败!");
//关闭文件
file.close();
}
}
void ServerWidget::sendData()
{
qint64 len = 0;
do{
//一次发送的大小
char buf[BUF_SIZE] = {0};
len = 0;
len = file.read(buf,BUF_SIZE);  //len为读取的字节数
len = tcpSocket->write(buf,len);    //len为发送的字节数
//已发数据累加
sendSize += len;
}while(len > 0);
}


客户端头文件将sendSize改为recvSize、添加一个布尔类型的isStart标志位,用来判断头部是否已经发送并给接收、少一个选择文件路径的槽函数(也可以不少,而可指定下载路径)。

#include "clientwidget.h"
#include "ui_clientwidget.h"

#include <QByteArray>
#include <QMessageBox>
ClientWidget::ClientWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ClientWidget)
{
ui->setupUi(this);
ui->progressBar->setValue(0);
tcpSocket = new QTcpSocket(this);

connect(tcpSocket,&QTcpSocket::readyRead,
[=](){
QByteArray buf = tcpSocket->readAll();
if(true == isStart){
isStart = false;
//接收包头
fileName = QString(buf).section("##",0,0);
fileSize = QString(buf).section("##",1,1).toInt();
recvSize = 0;

QString str = QString("接收的文件:[%1:%2kB]").arg(fileName).arg(fileSize/1024);
setWindowTitle(str);

ui->progressBar->setMaximum(fileSize);
ui->progressBar->setMinimum(0);
ui->progressBar->setValue(0);

file.setFileName(fileName);
if(false == file.open(QIODevice::WriteOnly)){
QMessageBox::information(this,"Error","文件创建并打开失败!");
}
tcpSocket->write("FileHead recv");
}else{
//接收处理文件
qint64 len = file.write(buf);
recvSize += len;

ui->progressBar->setValue(recvSize);
if(recvSize == fileSize){//接收完毕
file.close();
//提示信息
QMessageBox::information(this,"完成","文件接收完成");
//回射信息
tcpSocket->write("file write done");
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
}
}
);

}
ClientWidget::~ClientWidget()
{
delete ui;
}

void ClientWidget::on_buttonConnect_clicked()
{
QString ip = ui->lineEditIp->text();
qint16 port = ui->lineEditPort->text().toInt();
QMessageBox::information(this,"连接状态","服务器连接成功");
tcpSocket->connectToHost(QHostAddress(ip),port);
isStart = true;

}


结果测试(最终对比复制的文件与原文件MD5是否相同):

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  tcp 文件传输 应用