QTCpSocket文件传输
来源:互联网 发布:和平电视直播软件 编辑:程序博客网 时间:2024/06/05 17:48
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*4namespace Ui {class ServerWidget;}class ServerWidget : public QWidget{ Q_OBJECTpublic: 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
/*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);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
客户端头文件将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;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
结果测试(最终对比复制的文件与原文件MD5是否相同):
- QTCpSocket文件传输
- QTCpSocket文件传输
- QTcpSocket
- QTcpsocket
- QTcpSocket 编程
- 文件传输
- 文件传输
- 文件传输
- 文件传输
- 文件传输
- 文件传输
- 文件传输
- 文件传输
- QTcpSocket read write buffer
- QTcpSocket read write buffer
- QTcpSocket通信乱码解决方法
- QTcpSocket类中文参考
- QTcpSocket的相关知识
- jzoj3501 【NOIP2013模拟联考15】消息传递(news) 树形dp
- 由 官场微小说:两个县长的命运 所想
- CentOS6.4 Ftp服务器远程主机关闭连接问题
- TensorFlow笔记:数据导出
- 正则表达式知识
- QTCpSocket文件传输
- iscsi磁盘共享
- Comparator 升序降序
- matlab基础——LinePlots and Subplots
- IT的道德与伦理
- MySQL的auto_increment功能
- leetcode题解-25. Reverse Nodes in k-Group
- 用HTML前端技术做一个登入了界面+一些小技巧
- 测试工作---手动测试