-
(2022.4.4)完整的课设已经挂上 github:iTomxy/TomChat
-
(2018.6.8)收到评论说那个下载工程贵,去看了一下下载页…卧槽要 11 个积分(我当初应该没这么猖狂敢这么开价,毕竟主要是从 Qt 社区抄来的代码,记得好像是设了 3 分来着?)…
不管怎么样,在此说明:工程的主要代码都在文章里贴出了,文末号称“完整代码”的下载链只是把整个工程打包了上传上去,没多什么东西的啊!
如果真的需要整个工程,要不就直接联系我让我发给你吧。
参考
第38篇 网络(八)TCP(二)
QT TCP socket通信(二)
效果
- 发送端
- 标签,提示状态信息
- 进度条,提示发送进度
- 选择按钮,单击弹出选文件对话框
- 发送按钮,单击开始发送
- 接收端
- 标签,提示状态信息
- 进度条,提示发送进度
- 监听按钮,单击开始监听端口
- 效果图
- 工程截图
- 运行效果
Notes
- 发送端取消按钮未实现,忽略…
- socket 向链接成功发送数据后,会发出
bytesWritten(qint64)
信号 - QDataStream 设置版本(
setVersion()
)时,发送端和接收端要设置成同一版本
主要代码
发送端
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QAbstractSocket>
class QByteArray;
class QFile;
class QString;
class QTcpSocket;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void start_transfer();
void continue_transfer(qint64);
void show_error(QAbstractSocket::SocketError);
void on_selectBtn_clicked();
void on_sendBtn_clicked();
private:
Ui::MainWindow *ui;
QTcpSocket *send;
QFile *file;
QString fileName;
/* 总数据大小,已发送数据大小,剩余数据大小,每次发送数据块大小 */
qint64 fileBytes, sentBytes, restBytes, loadBytes;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QByteArray>
#include <QDataStream>
#include <QFileDialog>
#include <QHostAddress>
#include <QIODevice>
#include <QString>
#include <QTcpSocket>
const quint16 PORT = 3333;
const qint64 LOADBYTES = 4 * 1024; // 4 kilo-byte
const int DATA_STREAM_VERSION = QDataStream::Qt_5_8;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
send = new QTcpSocket(this);
fileBytes = sentBytes = restBytes = 0;
loadBytes = LOADBYTES;
file = Q_NULLPTR;
ui->sendProg->setValue(0); // 进度条置零
ui->sendBtn->setEnabled(false); // 禁用发送按钮
ui->cancelBtn->setEnabled(false);
/* 连接已建立 -> 开始发数据 */
connect(send, SIGNAL(connected()),
this, SLOT(start_transfer()));
/* 数据已发出 -> 继续发 */
connect(send, SIGNAL(bytesWritten(qint64)),
this, SLOT(continue_transfer(qint64)));
/* socket出错 -> 错误处理 */
connect(send, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(show_error(QAbstractSocket::SocketError)));
}
MainWindow::~MainWindow()
{
delete ui;
delete send;
}
/*--- 点击选择按钮 ---*/
void MainWindow::on_selectBtn_clicked()
{
/* 开文件选择窗选文件,返回带路径文件名 */
fileName = QFileDialog::getOpenFileName(this);
if(!fileName.isEmpty())
{
ui->stLabel->setText(
QString("File %1 Opened!").arg(fileName));
ui->sendBtn->setEnabled(true);
}
else
ui->stLabel->setText(QString("*** FAIL OPENING FILE"));
}
/*--- 点击发送按钮 ---*/
void MainWindow::on_sendBtn_clicked()
{
/* 发送连接请求 */
send->connectToHost(QHostAddress::LocalHost, PORT);
sentBytes = 0;
ui->sendBtn->setEnabled(false);
ui->cancelBtn->setEnabled(true);
ui->stLabel->setText(QString("Linking..."));
}
/*--- 开始传送 ---*/
void MainWindow::start_transfer()
{
file = new QFile(fileName);
if(!file->open(QFile::ReadOnly))
{
ui->stLabel->setText(QString("*** FILE OPEN ERROR"));
qDebug() << "*** start_transfer(): File-Open-Error";
return;
}
fileBytes = file->size();
ui->sendProg->setValue(0);
ui->stLabel->setText(QString("Connection Established!"));
QByteArray buf;
QDataStream out(&buf, QIODevice::WriteOnly);
out.setVersion(DATA_STREAM_VERSION);
/* 无路径文件名 */
QString sfName = fileName.right(fileName.size() -
fileName.lastIndexOf('/') - 1);
/* 首部 = 总大小 + 文件名长度 + 文件名 */
out << qint64(0) << qint64(0) << sfName;
/* 总大小加上首部的大小 */
fileBytes += buf.size();
ui->sendProg->setMaximum(fileBytes);
/* 重写首部的前两个长度字段 */
out.device()->seek(0);
out << fileBytes << (qint64(buf.size()) - 2 * sizeof(qint64));
/* 发送首部,计算剩余大小 */
restBytes = fileBytes - send->write(buf);
}
/*--- 继续传输 ---*/
void MainWindow::continue_transfer(qint64 sentSize)
{
sentBytes += sentSize;
ui->sendProg->setValue(sentBytes);
/* 还有数据要发 */
if(restBytes > 0)
{
/* 从文件读数据 */
QByteArray buf = file->read(qMin(loadBytes, restBytes));
/* 发送 */
restBytes -= send->write(buf);
}
else
file->close();
/* 全部发送完 */
if(sentBytes == fileBytes)
{
send->close(); // 关socket
fileName.clear(); // 清空文件名
ui->stLabel->setText(QString("Finish sending!"));
}
}
/*--- 出错处理 ---*/
void MainWindow::show_error(QAbstractSocket::SocketError)
{
qDebug() << "*** Socket Error";
send->close();
ui->stLabel->setText(QString("*** SOCKET ERROR, RESEND LATER"));
ui->sendBtn->setEnabled(true);
ui->sendProg->reset(); // 进度条归零
fileName.clear();
}
接收端
receivefile.h
#ifndef RECEVIEFILE_H
#define RECEVIEFILE_H
#include <QMainWindow>
#include <QAbstractSocket>
class QFile;
class QString;
class QTcpServer;
class QTcpSocket;
namespace Ui {
class RecevieFile;
}
class RecevieFile : public QMainWindow
{
Q_OBJECT
public:
explicit RecevieFile(QWidget *parent = 0);
~RecevieFile();
private slots:
void accept_connect();
void recevie_file();
void show_error(QAbstractSocket::SocketError);
void on_listenBtn_clicked();
private:
Ui::RecevieFile *ui;
QTcpServer *server;
QTcpSocket *receive;
QString fileName;
QFile *file;
/* 已接受数据,总数据,文件名长度 */
qint64 gotBytes, fileBytes, nameSize;
};
#endif // RECEVIEFILE_H
receivefile.cpp
- (2022.5.7)@qq_28885527 指出「对于一次性接收的文件不能传输」的问题,并给出修改方案,详见此评论。
#include "receviefile.h"
#include "ui_receviefile.h"
#include <iostream>
#include <QDataStream>
#include <QFile>
#include <QHostAddress>
#include <QTcpServer>
#include <QTcpSocket>
const quint16 PORT = 3333;
const int DATA_STREAM_VERSION = QDataStream::Qt_5_8;
RecevieFile::RecevieFile(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::RecevieFile)
{
ui->setupUi(this);
/* 进度条调零 */
ui->recvProg->setValue(0);
/* 启用监听按钮 */
ui->listenBtn->setEnabled(true);
fileBytes = gotBytes = nameSize = 0;
file = Q_NULLPTR;
receive = Q_NULLPTR;
server = new QTcpServer(this);
/* 连接请求 -> 接受连接 */
connect(server, SIGNAL(newConnection()),
this, SLOT(accept_connect()));
}
RecevieFile::~RecevieFile()
{
delete ui;
delete server;
}
/*--- 点击监听按钮 ---*/
void RecevieFile::on_listenBtn_clicked()
{
/* 禁用监听按钮 */
ui->listenBtn->setEnabled(false);
if(!server->listen(QHostAddress::Any, PORT))
{
std::cerr << "*** Listen to Port Failed ***" << std::endl;
qDebug() << server->errorString();
close();
ui->listenBtn->setEnabled(false);
return;
}
ui->stLabel->setText(QString("Listing to Port %1").arg(PORT));
}
/*--- 接受连接请求 ---*/
void RecevieFile::accept_connect()
{
receive = server->nextPendingConnection();
/* 有数据到 -> 接受数据 */
connect(receive, SIGNAL(readyRead()),
this, SLOT(recevie_file()));
/* socket出错 -> 出错处理 */
connect(receive, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(show_error(QAbstractSocket::SocketError)));
ui->stLabel->setText(QString("Connection Established!"));
gotBytes = 0;
server->close();
}
/*--- 接收文件 ---*/
void RecevieFile::recevie_file()
{
QDataStream in(receive);
in.setVersion(DATA_STREAM_VERSION);
/* 首部未接收/未接收完 */
if(gotBytes <= 2 * sizeof(qint64))
{
if(!nameSize) // 前两个长度字段未接收
{
if(receive->bytesAvailable() >= 2 * sizeof(qint64))
{
in >> fileBytes >> nameSize;
gotBytes += 2 * sizeof(qint64);
ui->recvProg->setMaximum(fileBytes);
ui->recvProg->setValue(gotBytes);
}
else // 数据不足,等下次
return;
}
else if(receive->bytesAvailable() >= nameSize)
{
in >> fileName;
gotBytes += nameSize;
ui->recvProg->setValue(gotBytes);
std::cout << "--- File Name: "
<< fileName.toStdString() << std::endl;
}
else // 数据不足文件名长度,等下次
return;
}
/* 已读文件名、文件未打开 -> 尝试打开文件 */
if(!fileName.isEmpty() && file == Q_NULLPTR)
{
file = new QFile(fileName);
if(!file->open(QFile::WriteOnly)) // 打开失败
{
std::cerr << "*** File Open Failed ***" << std::endl;
delete file;
file = Q_NULLPTR;
return;
}
ui->stLabel->setText(QString("Open %1 Successfully!").arg(fileName));
}
if(file == Q_NULLPTR) // 文件未打开,不能进行后续操作
return;
if(gotBytes < fileBytes) // 文件未接收完
{
gotBytes += receive->bytesAvailable();
ui->recvProg->setValue(gotBytes);
file->write(receive->readAll());
}
if(gotBytes == fileBytes) // 文件接收完
{
receive->close(); // 关socket
file->close(); // 关文件
delete file;
ui->stLabel->setText(QString("Finish receiving %1").arg(fileName));
ui->listenBtn->setEnabled(true);
}
}
/*--- 出错处理 ---*/
void RecevieFile::show_error(QAbstractSocket::SocketError)
{
std::cerr << "*** Socket Error ***" << std::endl;
qDebug() << receive->errorString();
receive->close(); // 关cocket
receive = Q_NULLPTR;
file = Q_NULLPTR;
fileName.clear(); // 清空文件名
fileBytes = gotBytes = nameSize = 0;
ui->recvProg->reset(); // 进度条归零
ui->listenBtn->setEnabled(true); // 启用监听按钮
ui->stLabel->setText(QString("*** SOCKET ERROR"));
}
完整工程
Qt实现TCP文件传输例子
←
\leftarrow
← 这是对应的整个工程,贼贵,还没多出什么实际有用的代码
→
\rightarrow
→ 完整课设
←
\leftarrow
← 不如下这个,需要的积分更少