Qt_客户端服务器通讯软件
服务器部分

基于Qt的服务器端实现,核心功能包括:监听客户端连接、接收聊天消息、接收并保存文件。
1. main.cpp(服务器入口)
//main.cpp
#include "filetransfertoolsdialog.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
FileTransferToolsDialog w;
w.show();
return a.exec();
}
初始化Qt应用,创建并显示FileTransferToolsDialog窗口。
2. filetransfertoolsdialog.h(定义服务器对话框类)
//filetransfertoolsdialog.h
#ifndef FILETRANSFERTOOLSDIALOG_H
#define FILETRANSFERTOOLSDIALOG_H
#include <QDateTime>
#include <QDialog>
#include <QFile>
#include <QMessageBox>
#include <QPainter>
#include <QTcpserver>
#include <QtDebug>
#include <QtNetwork>
QT_BEGIN_NAMESPACE
namespace Ui {
class FileTransferToolsDialog;
}
QT_END_NAMESPACE
class FileTransferToolsDialog : public QDialog {
Q_OBJECT
public:
FileTransferToolsDialog(QWidget *parent = nullptr);
~FileTransferToolsDialog();
// 网络通信组件
QTcpServer *P_TCPServerObject; // TCP服务器
QTcpSocket *P_TCPTextSocketObject; // 文本传输socket
QTcpSocket *P_TCPFileSocketObject; // 文件传输socket
// 文件传输相关变量
qint64 m_FileTotalBytes; // 文件总字节数
qint64 m_FileBytesReceived; // 已接收字节数
qint64 m_FileNameSizes; // 文件名长度
QString m_FileNames; // 文件名
QFile *m_LocalFiles; // 本地文件对象
QByteArray m_InBlocks; // 接收数据缓冲区
// 功能方法
QString GetLocalIpAddressFunc(); // 获取本地IP地址
void paintEvent(QPaintEvent *ev); // 重绘事件处理
void setupUIStyle(); // 设置UI样式
protected:
void closeEvent(QCloseEvent *event); // 窗口关闭事件
private slots:
// 服务器槽函数
void OnNewConnectionFunc(); // 处理新连接
// 文本通道槽函数
void OnClientDisconnectedFunc(); // 客户端断开连接
void OnSocketReadyReadFunc(); // 读取文本数据
// 文件通道槽函数
void OnFileClientConnectedFunc(); // 文件客户端连接
void OnFileClientDisconnectedFunc(); // 文件客户端断开
// 业务逻辑槽函数
void UpdateServerProgressFunc(); // 更新进度条
void DisplayErrorInfoFunc(
QAbstractSocket::SocketError socketError); // 显示错误信息
// UI槽函数
void on_pushButton_StartListening_clicked();
void on_pushButton_StopListening_clicked();
void on_pushButton_ExitSystem_clicked();
void on_pushButton_ClearMsg_clicked();
void on_pushButton_SendMsg_clicked();
private:
Ui::FileTransferToolsDialog *ui;
};
#endif // FILETRANSFERTOOLSDIALOG_H
核心成员
QTcpServer:P_TCPServerObject监听客户端连接
QTcpSocket:P_TCPTextSocketObject处理聊天消息、P_TCPFileSocketObject处理文件传输
文件传输变量:文件总大小、已接收、文件名、缓存区QByteArray
主要功能函数
GetLocalIpAddressFunc():获取本地IPv4地址
paintEvent():设置背景
closeEvent():停止监听
主要槽函数
连接:OnNewConnectionFunc:接受新的客户端连接(区分文本通道和文件通道)
文本通道相关:OnSocketReadyReadFunc、OnClientDisconnectedFunc
文件通道:UpdateServerProgressFunc、OnFileClientDisconnectedFunc
其它:启动/停止监听、清空消息、发送消息
3. filetransfertoolsdialog.cpp(服务器实现)
//filetransfertoolsdialog.cpp
#include "filetransfertoolsdialog.h"
#include "ui_filetransfertoolsdialog.h"
FileTransferToolsDialog::FileTransferToolsDialog(QWidget *parent)
: QDialog(parent), ui(new Ui::FileTransferToolsDialog) {
ui->setupUi(this);
// 设置UI样式
setupUIStyle();
setFixedSize(this->width(), this->height());
// 初始化文件传输变量
m_FileTotalBytes = 0;
m_FileBytesReceived = 0;
m_FileNameSizes = 0;
// 创建TCP服务器和套接字
P_TCPServerObject = new QTcpServer(this);
connect(P_TCPServerObject, SIGNAL(newConnection()), this,
SLOT(OnNewConnectionFunc()));
P_TCPTextSocketObject = new QTcpSocket(this);
P_TCPFileSocketObject = new QTcpSocket(this);
// 设置IP地址
QString strIp = GetLocalIpAddressFunc();
ui->comboIP->addItem(strIp);
qDebug() << strIp;
// 初始化按钮状态
ui->pushButton_SendMsg->setEnabled(false);
ui->pushButton_StopListening->setEnabled(false);
ui->pushButton_StartListening->setEnabled(true);
}
FileTransferToolsDialog::~FileTransferToolsDialog() { delete ui; }
// 设置UI样式
void FileTransferToolsDialog::setupUIStyle() {
QStringList labels = {"label_server", "label_port", "groupBox_msglist",
"groupBox_sendmsg", "label_fileprogress"};
for (const QString &labelName : labels) {
QWidget *widget = findChild<QWidget *>(labelName);
if (widget) {
QPalette palette = widget->palette();
palette.setColor(QPalette::WindowText, Qt::yellow);
widget->setPalette(palette);
QFont font = widget->font();
font.setBold(true);
widget->setFont(font);
}
}
}
QString FileTransferToolsDialog::GetLocalIpAddressFunc() {
QString hostName = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName(hostName);
QString StrLocalIP = "";
QList<QHostAddress> addList = hostInfo.addresses();
if (!addList.isEmpty()) {
for (int i = 0; i < addList.count(); i++) {
QHostAddress aHost = addList.at(i);
if (aHost.protocol() == QAbstractSocket::IPv4Protocol) {
StrLocalIP = aHost.toString();
break;
}
}
}
return StrLocalIP;
}
void FileTransferToolsDialog::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
painter.drawPixmap(rect(), QPixmap(":/new/prefix1/images/bgpng4.png"),
QRect());
}
void FileTransferToolsDialog::closeEvent(QCloseEvent *event) {
if (P_TCPServerObject->isListening()) {
P_TCPServerObject->close();
}
event->accept();
}
void FileTransferToolsDialog::OnNewConnectionFunc() {
// 处理文本传输通道
if (P_TCPTextSocketObject->state() == QAbstractSocket::UnconnectedState) {
P_TCPTextSocketObject = P_TCPServerObject->nextPendingConnection();
connect(P_TCPTextSocketObject, SIGNAL(disconnected()), this,
SLOT(OnClientDisconnectedFunc()));
connect(P_TCPTextSocketObject, SIGNAL(readyread()), this,
SLOT(OnSocketReadyReadFunc()));
ui->plainTextEdit_ListMsg->appendPlainText("\n[有客户端连接]*******");
return;
}
// 处理文件传输通道
if (P_TCPFileSocketObject->state() == QAbstractSocket::UnconnectedState) {
P_TCPFileSocketObject = P_TCPServerObject->nextPendingConnection();
connect(P_TCPFileSocketObject, SIGNAL(connected()), this,
SLOT(OnFileClientConnectedFunc()));
connect(P_TCPFileSocketObject, SIGNAL(disconnected()), this,
SLOT(OnFileClientDisconnectedFunc()));
connect(P_TCPFileSocketObject, SIGNAL(readyread()), this,
SLOT(UpdateServerProgressFunc()));
connect(P_TCPFileSocketObject, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(DisplayErrorInfoFunc));
}
ui->plainTextEdit_ListMsg->appendPlainText("\n[有客户端连接]*******");
}
void FileTransferToolsDialog::OnClientDisconnectedFunc() {
P_TCPTextSocketObject->close();
ui->plainTextEdit_ListMsg->appendPlainText(
"[********客户端已断开和服务器的连接********");
}
void FileTransferToolsDialog::OnSocketReadyReadFunc() {
QDateTime currentDateTime = QDateTime::currentDateTime();
QString strtemp = currentDateTime.toString("yyyy/mm/dd hh:mm:ss");
while (P_TCPTextSocketObject->canReadLine()) {
ui->plainTextEdit_ListMsg->appendPlainText(
"[客户端消息" + strtemp + "]:" + P_TCPTextSocketObject->readLine());
}
}
void FileTransferToolsDialog::OnFileClientConnectedFunc() {
ui->plainTextEdit_ListMsg->appendPlainText("[文件传输通道已连接]");
}
void FileTransferToolsDialog::OnFileClientDisconnectedFunc() {
P_TCPFileSocketObject->close();
}
void FileTransferToolsDialog::UpdateServerProgressFunc() {
QString strtemp =
QDateTime::currentDateTime().toString("yyyy/mm/dd hh:mm:ss");
QDataStream in(P_TCPFileSocketObject);
// 接收文件头信息
if (m_FileBytesReceived <= sizeof(qint64) * 2) {
if ((P_TCPFileSocketObject->bytesAvailable() >= sizeof(qint64) * 2) &&
(m_FileNameSizes == 0)) {
in >> m_FileTotalBytes >> m_FileNameSizes;
m_FileBytesReceived += sizeof(qint64) * 2;
}
if ((P_TCPFileSocketObject->bytesAvailable() >= m_FileNameSizes) &&
(m_FileNameSizes != 0)) {
in >> m_FileNames;
ui->plainTextEdit_EditMsg->appendPlainText(
tr("[%1 开始接收文件 %2 ...]").arg(strtemp).arg(m_FileNames));
m_FileBytesReceived += m_FileNameSizes;
m_LocalFiles =
new QFile(QApplication::applicationDirPath() + "\\" + m_FileNames);
if (!m_LocalFiles->open(QFile::WriteOnly)) {
qDebug() << "文件打开失败,请重新检查!";
delete m_LocalFiles;
m_LocalFiles = nullptr;
return;
}
} else {
return;
}
}
// 接收文件内容
if (m_FileBytesReceived < m_FileTotalBytes) {
m_InBlocks = P_TCPFileSocketObject->readAll();
m_LocalFiles->write(m_InBlocks);
m_FileBytesReceived += m_InBlocks.size();
m_InBlocks.clear();
ui->progressBar->setMaximum(m_FileTotalBytes);
ui->progressBar->setValue(m_FileBytesReceived);
}
// 传输完成处理
if (m_FileBytesReceived == m_FileTotalBytes) {
m_LocalFiles->close();
ui->plainTextEdit_ListMsg->appendPlainText(
QString("[%1 文件 %2 接收成功]").arg(strtemp).arg(m_FileNames));
m_FileTotalBytes = 0;
m_FileBytesReceived = 0;
m_FileNameSizes = 0;
delete m_LocalFiles;
m_LocalFiles = nullptr;
}
}
void FileTransferToolsDialog::DisplayErrorInfoFunc(
QAbstractSocket::SocketError socketError) {
P_TCPFileSocketObject->close();
ui->progressBar->reset();
}
// UI槽函数
void FileTransferToolsDialog::on_pushButton_StartListening_clicked() {
QString strIp = ui->comboIP->currentText();
quint16 port = ui->spinPort->value();
if (P_TCPServerObject->listen(QHostAddress(strIp), port)) {
ui->plainTextEdit_ListMsg->appendPlainText("[服务器开始监听]");
ui->plainTextEdit_ListMsg->appendPlainText(
"[IP地址:" + strIp + "][端口:" + QString::number(port) + "]");
ui->pushButton_StartListening->setEnabled(false);
ui->pushButton_StopListening->setEnabled(true);
ui->pushButton_SendMsg->setEnabled(true);
}
}
void FileTransferToolsDialog::on_pushButton_StopListening_clicked() {
if (P_TCPServerObject->isListening()) {
P_TCPServerObject->close();
ui->plainTextEdit_ListMsg->appendPlainText("[服务器已停止监听]");
ui->pushButton_StartListening->setEnabled(true);
ui->pushButton_StopListening->setEnabled(false);
ui->pushButton_SendMsg->setEnabled(false);
}
}
void FileTransferToolsDialog::on_pushButton_ExitSystem_clicked() {
this->close();
}
void FileTransferToolsDialog::on_pushButton_ClearMsg_clicked() {
ui->plainTextEdit_ListMsg->clear();
}
void FileTransferToolsDialog::on_pushButton_SendMsg_clicked() {
QString strMsg = ui->plainTextEdit_EditMsg->toPlainText().trimmed();
if (strMsg.isEmpty()) {
QMessageBox::critical(this, "错误", "系统提示:消息内容不能为空!");
return;
}
QString strtemp =
QDateTime::currentDateTime().toString("yyyy/mm/dd hh:mm:ss");
ui->plainTextEdit_ListMsg->appendPlainText("[服务器消息 " + strtemp +
"]:" + strMsg);
P_TCPTextSocketObject->write((strMsg + "\n").toUtf8());
ui->plainTextEdit_EditMsg->clear();
}
通过 QTcpServer + 双 socket(分别负责消息通道与文件通道),保证了消息与文件传输互不干扰。文件接收过程分阶段处理:文件头 ---文件数据 ---完成确认。
FileTransferToolsDialog::FileTransferToolsDialog(QWidget *parent)
: QDialog(parent), ui(new Ui::FileTransferToolsDialog) {
...//UI界面、窗口尺寸部分
// 初始化文件传输变量
m_FileTotalBytes = 0;
m_FileBytesReceived = 0;
m_FileNameSizes = 0;
// 创建TCP服务器和套接字
P_TCPServerObject = new QTcpServer(this);
connect(P_TCPServerObject, SIGNAL(newConnection()), this,
SLOT(OnNewConnectionFunc()));
P_TCPTextSocketObject = new QTcpSocket(this);
P_TCPFileSocketObject = new QTcpSocket(this);
// 设置IP地址
QString strIp = GetLocalIpAddressFunc();
ui->comboIP->addItem(strIp);
qDebug() << strIp;
// 初始化按钮状态
ui->pushButton_SendMsg->setEnabled(false);
ui->pushButton_StopListening->setEnabled(false);
ui->pushButton_StartListening->setEnabled(true);
}
初始化UI界面、窗口尺寸、网络组件以及按钮状态。
m_FileTotalBytes、m_FileBytesReceived等文件传输变量清零。
P_TCPServerObject作为监听端。connect()连接信号槽函数,当新连接到来newConnection()时,自动调用OnNewConnectionFunc()进行处理;
创建两个socket,分别负责消息通道与文件通道
用GetLocalIpAddressFunc()获取IPv4,填入comboIP,设置IP地址。
按钮状态,未监听时,只允许“开始监听”,禁用“停止监听”和“发送消息”。
void FileTransferToolsDialog::OnNewConnectionFunc() {
// 处理文本传输通道
if (P_TCPTextSocketObject->state() == QAbstractSocket::UnconnectedState) {
P_TCPTextSocketObject = P_TCPServerObject->nextPendingConnection();
connect(P_TCPTextSocketObject, SIGNAL(disconnected()), this,
SLOT(OnClientDisconnectedFunc()));
connect(P_TCPTextSocketObject, SIGNAL(readyread()), this,
SLOT(OnSocketReadyReadFunc()));
ui->plainTextEdit_ListMsg->appendPlainText("\n[有客户端连接]*******");
return;
}
// 处理文件传输通道
if (P_TCPFileSocketObject->state() == QAbstractSocket::UnconnectedState) {
P_TCPFileSocketObject = P_TCPServerObject->nextPendingConnection();
connect(P_TCPFileSocketObject, SIGNAL(connected()), this,
SLOT(OnFileClientConnectedFunc()));
connect(P_TCPFileSocketObject, SIGNAL(disconnected()), this,
SLOT(OnFileClientDisconnectedFunc()));
connect(P_TCPFileSocketObject, SIGNAL(readyread()), this,
SLOT(UpdateServerProgressFunc()));
connect(P_TCPFileSocketObject, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(DisplayErrorInfoFunc));
}
ui->plainTextEdit_ListMsg->appendPlainText("\n[有客户端连接]*******");
}
将QTcpServer队列里的新到连接,按文本通道、文件通道的socket的顺序先后分配出去,并绑定对应的槽函数。
优先分配文本套接字。若P_TCPTextSocketObject仍是UnconnectedState,用nextPendingConnection()取出队首的QTcpSocket(连接),进行赋值。并用connected()绑定信号和槽函数,随后立即return。若文本套接字已分配,再分配文件套接字。
void FileTransferToolsDialog::UpdateServerProgressFunc() {
QString strtemp =
QDateTime::currentDateTime().toString("yyyy/mm/dd hh:mm:ss");
QDataStream in(P_TCPFileSocketObject);
// 接收文件头信息
if (m_FileBytesReceived <= sizeof(qint64) * 2) {
if ((P_TCPFileSocketObject->bytesAvailable() >= sizeof(qint64) * 2) &&
(m_FileNameSizes == 0)) {
in >> m_FileTotalBytes >> m_FileNameSizes;
m_FileBytesReceived += sizeof(qint64) * 2;
}
if ((P_TCPFileSocketObject->bytesAvailable() >= m_FileNameSizes) &&
(m_FileNameSizes != 0)) {
in >> m_FileNames;
ui->plainTextEdit_EditMsg->appendPlainText(
tr("[%1 开始接收文件 %2 ...]").arg(strtemp).arg(m_FileNames));
m_FileBytesReceived += m_FileNameSizes;
m_LocalFiles =
new QFile(QApplication::applicationDirPath() + "\\" + m_FileNames);
if (!m_LocalFiles->open(QFile::WriteOnly)) {
qDebug() << "文件打开失败,请重新检查!";
delete m_LocalFiles;
m_LocalFiles = nullptr;
return;
}
} else {
return;
}
}
// 接收文件内容
if (m_FileBytesReceived < m_FileTotalBytes) {
m_InBlocks = P_TCPFileSocketObject->readAll();
m_LocalFiles->write(m_InBlocks);
m_FileBytesReceived += m_InBlocks.size();
m_InBlocks.clear();
ui->progressBar->setMaximum(m_FileTotalBytes);
ui->progressBar->setValue(m_FileBytesReceived);
}
// 传输完成处理
if (m_FileBytesReceived == m_FileTotalBytes) {
m_LocalFiles->close();
ui->plainTextEdit_ListMsg->appendPlainText(
QString("[%1 文件 %2 接收成功]").arg(strtemp).arg(m_FileNames));
m_FileTotalBytes = 0;
m_FileBytesReceived = 0;
m_FileNameSizes = 0;
delete m_LocalFiles;
m_LocalFiles = nullptr;
}
}
处理文件传输,分阶段接收文件头信息、写入文件内容并更新进度条,直到接收完整个文件。此部分的顺序以及数据类型必须与客户端完全匹配:文件大小(qint64)---文件名长度(qint64)---文件名(QString)---文件内容(二进制)。
1.解析文件头(总文件大小、文件名大小、文件名)
用QDataStream对象in处理有固定结构的文件头。检查当前已接收的字节数是否小于两个qint64(总文件大小和文件名大小)。若接收缓冲区数据量充足,先取出m_FileTotalBytes(文件总字节数)和m_FileNameSizes(文件名长度),同时累加到已接收m_FileBytesReceived。再根据m_FileNameSizes提取文件名m_FileNames,在UI中输出提示信息。最后应用程序运行目录下新建同名文件newQFile(...),准备写入。并作检验,若此文件打开失败,释放内存并置空指针。
2.接收文件内容
当m_FileBytesReceived<m_FileTotalBytes时:从P_TCPFileSocketObject中一次性读取所有可用数据readAll(),写入m_LocalFiles,更新进度条.
3.传输完成处理
若已接收字节数与总字节数相等,关闭并销毁m_LocalFiles,在消息列表输出“文件接收成功”。重置文件传输变量,以便下一次传输。
QString FileTransferToolsDialog::GetLocalIpAddressFunc() {
QString hostName = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName(hostName);
QString StrLocalIP = "";
QList<QHostAddress> addList = hostInfo.addresses();
if (!addList.isEmpty()) {
for (int i = 0; i < addList.count(); i++) {
QHostAddress aHost = addList.at(i);
if (aHost.protocol() == QAbstractSocket::IPv4Protocol) {
StrLocalIP = aHost.toString();
break;
}
}
}
return StrLocalIP;
}
自动获取本机可用的IPv4地址。
通过QHostInfo::localHostName()获取主机名,再调用QHostInfo::fromName()得到该主机对应的所有信息。遍历hostInfo.addresses()返回的QList<QHostAddress>,筛选其中符合QAbstractSocket::IPv4Protocol的地址。一旦找到IPv4地址,就将其转换为字符串并返回。若列表为空或未找到IPv4,返回空字符串("")。
void FileTransferToolsDialog::on_pushButton_SendMsg_clicked() {
QString strMsg = ui->plainTextEdit_EditMsg->toPlainText().trimmed();
if (strMsg.isEmpty()) {
QMessageBox::critical(this, "错误", "系统提示:消息内容不能为空!");
return;
}
QString strtemp =
QDateTime::currentDateTime().toString("yyyy/mm/dd hh:mm:ss");
ui->plainTextEdit_ListMsg->appendPlainText("[服务器消息 " + strtemp +
"]:" + strMsg);
P_TCPTextSocketObject->write((strMsg + "\n").toUtf8());
ui->plainTextEdit_EditMsg->clear();
}
处理用户输入并通过TCP套接字将消息发送到客户端,同时在消息窗口中显示发送记录。
从plainTextEdit_EditMsg输入框中读取用户输入,调用trimmed()去除首尾空格。若输入为空,用QMessageBox::critical()弹出提示框,阻止执行。生成带时间戳的消息记录,在消息列表框plainTextEdit_ListMsg中显示发送的消息。
调用P_TCPTextSocketObject->write()将消息内容以UTF-8编码形式发送给客户端。最后清空输入框plainTextEdit_EditMsg。
客户端部分

基于Qt的客户端实现,核心功能包括:消息聊天、文件传输、进度条显示、系统托盘等。
1. main.cpp(客户端入口)
//main.cpp
#include "maindialog.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainDialog w;
// 显示窗口最小化按钮
w.setWindowFlags(w.windowFlags() | Qt::WindowMinimizeButtonHint);
w.show();
return a.exec();
}
初始化Qt应用,创建并显示MainDialog窗口。用QApplication启动事件循环。
2. maindialog.h(定义客户端对话框类)
//maindialog.h
#ifndef MAINDIALOG_H
#define MAINDIALOG_H
#include <QAction>
#include <QDataStream>
#include <QDateTime>
#include <QDialog>
#include <QFile>
#include <QFileDevice>
#include <QFileDialog>
#include <QHostAddress>
#include <QHostInfo>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QPainter>
#include <QSystemTrayIcon>
#include <QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainDialog;
}
QT_END_NAMESPACE
class MainDialog : public QDialog {
Q_OBJECT
public:
MainDialog(QWidget *parent = nullptr);
~MainDialog();
private:
// 网络通信组件
QTcpSocket *m_TcpMsgClient; // 聊天消息socket
QTcpSocket *m_TcpFileClient; // 文件传输socket
// 文件传输相关变量
QFile *m_LocalFiles; // 本地文件对象
qint64 m_TotalBytes; // 文件总大小
qint64 m_BytesWrites; // 已发送字节数
qint64 m_BytesToWrites; // 剩余字节数
qint64 m_LoadSizes; // 每次发送大小
QString m_FileNames; // 文件名及路径
QByteArray m_OutDataBlock; // 发送数据缓冲区
// 系统托盘组件
QSystemTrayIcon *MySystemTrays; // 系统托盘
QMenu *pContextMenu; // 托盘菜单
QAction *qMiniAction; // 最小化动作
QAction *qRestoreAction; // 恢复动作
QAction *qExitAction; // 退出动作
// 功能方法
void paintEvent(QPaintEvent *event); // 重绘事件处理
QString GetLocalIPAddress(); // 获取本机IP地址
void closeEvent(QCloseEvent *event); // 窗口关闭事件
private slots:
// 网络连接槽函数
void OnConnectedFunc(); // 客户端连接成功
void OnDisconnectedFunc(); // 客户端断开连接
void OnSocketReadyReadFunc(); // 读取服务器数据
void DisplayErrorFunc(QAbstractSocket::SocketError); // 显示错误信息
// 文件传输槽函数
void UpdateClientProgressFunc(qint64); // 更新进度条
// 系统托盘槽函数
void MySystemTrayFunc(); // 系统托盘处理
// UI槽函数
void on_pushButton_ConnectServer_clicked();
void on_pushButton_DisconnectServer_clicked();
void on_pushButton_Exit_clicked();
void on_pushButton_SendMsg_clicked();
void on_pushButton_ClearMsg_clicked();
void on_pushButton_SelectFile_clicked();
void on_pushButton_SendFile_clicked();
private:
Ui::MainDialog *ui;
};
#endif // MAINDIALOG_H
核心成员(几乎完全与服务器端相对应)
两个QTcpSocket:m_TcpMsgClient处理聊天消息、m_TcpFileClient处理文件传输
文件传输相关变量:文件总大小、已发送字节数、剩余字节、每次发送大小、缓存区等
系统托盘相关成员:托盘图标、菜单和相关动作(最小化、恢复、退出)
槽函数
连接与断开服务器:(OnConnectedFunc、OnDisconnectedFunc)
消息收发:(OnSocketReadyReadFunc、on_pushButton_SendMsg_clicked)
文件选择与发送:(on_pushButton_SelectFile_clicked、on_pushButton_SendFile_clicked)
更新进度条:(UpdateClientProgressFunc)
异常处理:(DisplayErrorFunc)
3. maindialog.cpp(客户端实现)
//maindialog.cpp
#include "maindialog.h"
#include "ui_maindialog.h"
MainDialog::MainDialog(QWidget *parent)
: QDialog(parent), ui(new Ui::MainDialog) {
ui->setupUi(this);
ui->plainTextEdit_MsgList->setReadOnly(true);
setFixedSize(width(), height());
// 初始化文件传输变量
m_TotalBytes = 0;
m_BytesWrites = 0;
m_BytesToWrites = 0;
m_LoadSizes = 1024 * 4;
// 创建TCP套接字
m_TcpMsgClient = new QTcpSocket(this);
m_TcpFileClient = new QTcpSocket(this);
// 设置IP地址
QString strLocalIPs = GetLocalIPAddress();
QString strIP = ("本地IP地址:" + strLocalIPs);
ui->label_LocalIP->setText(strIP);
ui->comboBox_ServerIP->addItem(strLocalIPs);
// 连接信号槽
connect(m_TcpMsgClient, SIGNAL(connected()), this, SLOT(OnConnectedFunc()));
connect(m_TcpMsgClient, SIGNAL(disconnected()), this,
SLOT(OnDisconnectedFunc()));
connect(m_TcpMsgClient, SIGNAL(readyRead()), this,
SLOT(OnSocketReadyReadFunc()));
connect(m_TcpFileClient, SIGNAL(bytesWritten(qint64)), this,
SLOT(UpdateClientProgressFunc(qint64)));
connect(m_TcpFileClient, SIGNAL(error(QAbstractSocket::SocketError)), this,
SLOT(DisplayErrorFunc(QAbstractSocket::SocketError)));
MySystemTrayFunc();
}
MainDialog::~MainDialog() { delete ui; }
void MainDialog::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.drawPixmap(rect(), QPixmap(":/new/prefix1/images/bgpng.png"),
QRect());
}
QString MainDialog::GetLocalIPAddress() {
QString strHostName = QHostInfo::localHostName();
QHostInfo HostInfos = QHostInfo::fromName(strHostName);
QString strLocalIP = "";
QList<QHostAddress> addressList = HostInfos.addresses();
if (!addressList.isEmpty()) {
for (int i = 0; i < addressList.count(); i++) {
QHostAddress sHost = addressList.at(i);
if (sHost.protocol() == QAbstractSocket::IPv4Protocol) {
strLocalIP = sHost.toString();
break;
}
}
}
return strLocalIP;
}
void MainDialog::closeEvent(QCloseEvent *event) {
if (m_TcpMsgClient->state() == QAbstractSocket::ConnectedState)
m_TcpMsgClient->disconnectFromHost();
if (m_TcpFileClient->state() == QAbstractSocket::ConnectedState)
m_TcpFileClient->disconnectFromHost();
event->accept();
}
void MainDialog::OnConnectedFunc() {
ui->plainTextEdit_MsgList->appendPlainText(
"[*****客户端与服务器连接成功*****]");
ui->plainTextEdit_MsgList->appendPlainText(
"IP Address:" + m_TcpMsgClient->peerAddress().toString() +
"[Port:" + QString::number(m_TcpMsgClient->peerPort()) + "]");
ui->pushButton_ConnectServer->setEnabled(false);
ui->pushButton_DisconnectServer->setEnabled(true);
}
void MainDialog::OnDisconnectedFunc() {
ui->plainTextEdit_MsgList->appendPlainText(
"[客户端已断开与服务器的连接!]\n");
ui->pushButton_ConnectServer->setEnabled(true);
ui->pushButton_DisconnectServer->setEnabled(false);
}
void MainDialog::OnSocketReadyReadFunc() {
QString strDateTimes =
QDateTime::currentDateTime().toString("yyyy/mm/dd hh:mm:ss");
while (m_TcpMsgClient->canReadLine())
ui->plainTextEdit_MsgList->appendPlainText(
"[服务器消息 " + strDateTimes + "]" + m_TcpMsgClient->readLine());
}
void MainDialog::MySystemTrayFunc() {
QIcon qIcons(":/new/prefix1/images/logo.ico");
MySystemTrays = new QSystemTrayIcon(this);
MySystemTrays->setIcon(qIcons);
MySystemTrays->setToolTip("Tcp客户端!");
qMiniAction = new QAction("客户端最小化", this);
connect(qMiniAction, SIGNAL(triggered()), this, SLOT(hide()));
qRestoreAction = new QAction("恢复客户端", this);
connect(qRestoreAction, SIGNAL(triggered()), this, SLOT(showNormal()));
qExitAction = new QAction("退出系统", this);
connect(qExitAction, SIGNAL(triggered()), this, SLOT(quit()));
pContextMenu = new QMenu(this);
pContextMenu->addAction(qMiniAction);
pContextMenu->addAction(qRestoreAction);
pContextMenu->addAction(qExitAction);
MySystemTrays->setContextMenu(pContextMenu);
// MySystemTrays->show();
}
void MainDialog::UpdateClientProgressFunc(qint64 numBytes) {
QString strDateTimes =
QDateTime::currentDateTime().toString("yyyy/mm/dd hh:mm:ss");
m_BytesWrites += (int)numBytes;
if (m_BytesToWrites > 0) {
// 从文件中读取数据块(4KB)
m_OutDataBlock = m_LocalFiles->read(qMin(m_BytesToWrites, m_LoadSizes));
// 发送数据并更新剩余字节数
m_BytesToWrites -= (int)m_TcpFileClient->write(m_OutDataBlock);
m_OutDataBlock.resize(0);
} else {
m_LocalFiles->close();
}
// 更新进度条
ui->progressBar->setMaximum(m_TotalBytes);
ui->progressBar->setValue(m_BytesWrites);
if (m_BytesWrites == m_TotalBytes) {
m_LocalFiles->close();
ui->plainTextEdit_MsgList->appendPlainText(
QString("[%1 文件已传输完成!!!路径为:%2]")
.arg(strDateTimes)
.arg(m_FileNames));
}
}
void MainDialog::DisplayErrorFunc(QAbstractSocket::SocketError) {
m_TcpFileClient->close();
ui->progressBar->reset();
}
// UI槽函数
void MainDialog::on_pushButton_ConnectServer_clicked() {
QString ipaddress = ui->comboBox_ServerIP->currentText();
quint16 port = ui->spinBox_ServerPort->value();
// 连接消息传输套接字
m_TcpMsgClient->connectToHost(ipaddress, port);
// 连接文件传输套接字
m_TcpFileClient->connectToHost(ipaddress, port);
}
void MainDialog::on_pushButton_DisconnectServer_clicked() {
// 断开消息传输套接字
if (m_TcpMsgClient->state() == QAbstractSocket::ConnectedState)
m_TcpMsgClient->disconnectFromHost();
// 断开文件传输套接字
if (m_TcpFileClient->state() == QAbstractSocket::ConnectedState)
m_TcpFileClient->disconnectFromHost();
}
void MainDialog::on_pushButton_Exit_clicked() {
m_TcpMsgClient->close();
m_TcpFileClient->close();
this->close();
}
void MainDialog::on_pushButton_SendMsg_clicked() {
QString strDateTimes =
QDateTime::currentDateTime().toString("yyyy/mm/dd hh:mm:ss");
QString strMsg = ui->plainTextEdit_SendMsg->toPlainText();
if (strMsg.isEmpty()) {
QMessageBox::critical(this, "错误", "发消息为空!", QMessageBox::Yes);
ui->plainTextEdit_SendMsg->setFocus();
return;
}
ui->plainTextEdit_MsgList->appendPlainText("[客户端消息 " + strDateTimes +
"]:" + strMsg);
ui->plainTextEdit_SendMsg->clear();
ui->plainTextEdit_SendMsg->setFocus();
QByteArray bytearray = strMsg.toUtf8();
bytearray.append('\n');
m_TcpMsgClient->write(bytearray);
}
void MainDialog::on_pushButton_ClearMsg_clicked() {
ui->plainTextEdit_MsgList->clear();
}
void MainDialog::on_pushButton_SelectFile_clicked() {
QString strDateTimes =
QDateTime::currentDateTime().toString("yyyy/mm/dd hh:mm:ss");
m_FileNames = QFileDialog::getOpenFileName(this, "请选择要传输的文件");
if (!m_FileNames.isEmpty()) {
ui->plainTextEdit_MsgList->appendPlainText(
QString("[%1 客户端打开文件:%2 成功!]")
.arg(strDateTimes)
.arg(m_FileNames));
ui->pushButton_SendFile->setEnabled(true);
}
}
void MainDialog::on_pushButton_SendFile_clicked() {
// 检查文件传输套接字是否连接
if (m_TcpFileClient->state() != QAbstractSocket::ConnectedState) {
QString ipaddress = ui->comboBox_ServerIP->currentText();
quint16 port = ui->spinBox_ServerPort->value();
m_TcpFileClient->connectToHost(ipaddress, port);
return; // 等待连接完成后再发送
}
m_LocalFiles = new QFile(m_FileNames);
if (!m_LocalFiles->open(QFile::ReadOnly)) {
qDebug() << "打开文件错误,请检查!" << endl;
return;
}
m_TotalBytes = m_LocalFiles->size();
QDataStream sendDataOut(&m_OutDataBlock, QIODevice::WriteOnly);
QString strCurrentFileName =
m_FileNames.right(m_FileNames.size() - m_FileNames.lastIndexOf('/') - 1);
// 写入文件头信息
sendDataOut << qint64(0) << qint64(0) << strCurrentFileName;
m_TotalBytes += m_OutDataBlock.size();
sendDataOut.device()->seek(0);
sendDataOut << m_TotalBytes
<< qint64((m_OutDataBlock.size() - sizeof(qint64) * 2));
m_BytesToWrites = m_TotalBytes - m_TcpFileClient->write(m_OutDataBlock);
m_OutDataBlock.resize(0);
}
初始化客户端系统。设置UI界面,绑定网络通信变量与信号槽函数。
MainDialog::MainDialog(QWidget *parent)
: QDialog(parent), ui(new Ui::MainDialog) {
ui->setupUi(this);
ui->plainTextEdit_MsgList->setReadOnly(true);
setFixedSize(width(), height());
// 初始化文件传输变量
m_TotalBytes = 0;
m_BytesWrites = 0;
m_BytesToWrites = 0;
m_LoadSizes = 1024 * 4;
// 创建TCP套接字
m_TcpMsgClient = new QTcpSocket(this);
m_TcpFileClient = new QTcpSocket(this);
// 设置IP地址
QString strLocalIPs = GetLocalIPAddress();
QString strIP = ("本机IP地址:" + strLocalIPs);
ui->label_LocalIP->setText(strIP);
ui->comboBox_ServerIP->addItem(strLocalIPs);
// 连接信号槽
connect(m_TcpMsgClient, SIGNAL(connected()), this, SLOT(OnConnectedFunc()));
connect(m_TcpMsgClient, SIGNAL(disconnected()), this,
SLOT(OnDisconnectedFunc()));
connect(m_TcpMsgClient, SIGNAL(readyRead()), this,
SLOT(OnSocketReadyReadFunc()));
connect(m_TcpFileClient, SIGNAL(bytesWritten(qint64)), this,
SLOT(UpdateClientProgressFunc(qint64)));
connect(m_TcpFileClient, SIGNAL(error(QAbstractSocket::SocketError)), this,
SLOT(DisplayErrorFunc(QAbstractSocket::SocketError)));
MySystemTrayFunc();
}
初始化文件传输相关的内部变量(m_TotalBytes、m_BytesWrites等)。
新建两个TCP套接字分别用于文本消息、和文件传输处理(与服务器端相对应)。
调用GetLocalIPAddress()获取本机IP地址,并将其显示在界面标签label_LocalIP中,并加入到下拉框comboBox_ServerIP。当m_TcpMsgClient连接成功、断开连接、有数据可读、发生错误或文件写入进度更新时时,触发相应的槽函数。
最后调用MySystemTrayFunc()初始化系统托盘功能。
void MainDialog::on_pushButton_SendFile_clicked() {
m_LocalFiles = new QFile(m_FileNames);
if (!m_LocalFiles->open(QFile::ReadOnly)) {
qDebug() << "打开文件错误,请检查!" << endl;
return;
}
m_TotalBytes = m_LocalFiles->size();
QDataStream sendDataOut(&m_OutDataBlock, QIODevice::WriteOnly);
QString strCurrentFileName =
m_FileNames.right(m_FileNames.size() - m_FileNames.lastIndexOf('/') - 1);
// 写入文件头信息
sendDataOut << qint64(0) << qint64(0) << strCurrentFileName;
m_TotalBytes += m_OutDataBlock.size(b);
sendDataOut.device()->seek(0);
sendDataOut << m_TotalBytes
<< qint64((m_OutDataBlock.size() - sizeof(qint64) * 2));
m_BytesToWrites = m_TotalBytes - m_TcpFileClient->write(m_OutDataBlock);
m_OutDataBlock.resize(0);
}
响应“发送文件”按钮的点击事件,准备本地文件并通过TCP套接字进行传输。
首先,根据用户选择的文件路径m_FileNames创建QFile对象m_LocalFiles,尝试以只读方式打开。若失败,输出提示错误信息并终止。
若成功,通过m_LocalFiles->size()获取文件大小,存入m_TotalBytes以便后续统计进度。用QDataStreamsendDataOut向缓存区m_OutDataBlock写入文件头信息(此部分与服务器端完全对应一致)随后,函数将文件的总大小(文件本身大小+文件头大小)和文件名大小回填到前两个占位字段中。使接收端首先读取到总大小与文件名信息。最后,通过m_TcpFileClient->write(m_OutDataBlock)将准备好的文件头一次性写入,并计算尚未发送的字节数m_BytesToWrites,同时清空m_OutDataBlock。
void MainDialog::UpdateClientProgressFunc(qint64 numBytes) {
QString strDateTimes =
QDateTime::currentDateTime().toString("yyyy/mm/dd hh:mm:ss");
m_BytesWrites += (int)numBytes;
if (m_BytesToWrites > 0) {
// 从文件中读取数据块(4KB)
m_OutDataBlock = m_LocalFiles->read(qMin(m_BytesToWrites, m_LoadSizes));
// 发送数据并更新剩余字节数
m_BytesToWrites -= (int)m_TcpFileClient->write(m_OutDataBlock);
m_OutDataBlock.resize(0);
} else {
m_LocalFiles->close();
}
// 更新进度条
ui->progressBar->setMaximum(m_TotalBytes);
ui->progressBar->setValue(m_BytesWrites);
if (m_BytesWrites == m_TotalBytes) {
m_LocalFiles->close();
ui->plainTextEdit_MsgList->appendPlainText(
QString("[%1 文件已传输完成!!!路径为:%2]")
.arg(strDateTimes)
.arg(m_FileNames));
}
}
处理文件发送过程中的UI进度更新。每当TCP套接字成功写入一部分数据时,该槽函数会被自动触发。
附加时间戳后,将本次写入的字节数累加到m_BytesWrites中,用于统计已发送的总量。如果还有未发送的字节,就从本地文件m_LocalFiles中读取一个数据块,大小为剩余字节数与缓存块大小m_LoadSizes(4KB)中的较小值。读取后的数据被写入m_TcpFileClient,并更新剩余待发送的字节数。完成后,清空m_OutDataBlock,以便下次使用。若剩余字节数为零,说明文件数据已经全部写入,此时关闭文件m_LocalFiles。
函数也会实时更新UI界面的进度条:progressBar的最大值设为文件总字节数m_TotalBytes,当前值设为已写入字节数m_BytesWrites。当m_BytesWrites与m_TotalBytes相等时,说明整个文件传输已经完成。此时关闭文件,并在消息显示区plainTextEdit_MsgList输出提示信息。
void MainDialog::MySystemTrayFunc() {
QIcon qIcons(":/new/prefix1/images/logo.ico");
MySystemTrays = new QSystemTrayIcon(this);
MySystemTrays->setIcon(qIcons);
MySystemTrays->setToolTip("Tcp客户端!");
qMiniAction = new QAction("客户端最小化", this);
connect(qMiniAction, SIGNAL(triggered()), this, SLOT(hide()));
qRestoreAction = new QAction("恢复客户端", this);
connect(qRestoreAction, SIGNAL(triggered()), this, SLOT(showNormal()));
qExitAction = new QAction("退出系统", this);
connect(qExitAction, SIGNAL(triggered()), this, SLOT(quit()));
pContextMenu = new QMenu(this);
pContextMenu->addAction(qMiniAction);
pContextMenu->addAction(qRestoreAction);
pContextMenu->addAction(qExitAction);
MySystemTrays->setContextMenu(pContextMenu);
MySystemTrays->show();
}
实现系统托盘功能。应用能在后台运行,并通过托盘图标进行操作。
通过QIcon设置了托盘图标logo.ico,并将其绑定到QSystemTrayIcon对象MySystemTrays,同时设置提示信息。接着,定义三个动作:qMiniAction(客户端最小化)、qRestoreAction(恢复客户端)、qExitAction(退出系统)。将三项动作加入到右键菜单pContextMenu,并通过MySystemTrays->setContextMenu()绑定到托盘图标。最后调用MySystemTrays->show(),使托盘图标正式显示在系统任务栏。
更多推荐



所有评论(0)