一、多线程的重要性
在单线程环境中,所有任务都在同一个线程中执行。这意味着如果一个任务需要等待(例如网络通信中的阻塞读取操作),整个程序就会卡住,直到该任务完成。在用户界面应用程序中,这会导致界面冻结,用户体验极差。
通过将网络通信操作放入一个单独的线程中,我们可以使主线程专注于处理用户交互和界面更新,从而提高应用程序的响应能力。在 Qt 中,QThread
提供了一个强大的多线程支持,它允许我们轻松地将耗时的操作放入后台线程执行。
二、ClientThread
类的详细实现
ClientThread
类是我们实现的多线程 TCP 客户端的核心。它继承自 QThread
,并重写了 run()
方法以在新线程中执行网络操作。
class ClientThread : public QThread
{
Q_OBJECT
public:
ClientThread(QObject *parent);
~ClientThread();
private:
QTcpSocket* client; // 用于TCP连接的QTcpSocket对象
QString IP; // 服务器的IP地址
quint16 port; // 服务器的端口号
bool isStopped; // 控制线程停止的标志
public:
void connectToServer(const QString& IP, quint16 port); // 连接到服务器
void addSendData(const QByteArray& data); // 填充要发送的数据
void stop(); // 停止线程
protected:
void run() override; // 重写QThread的run()方法
signals:
void connectionResult(bool success); // 连接结果信号
void dataReceived(int data); // 数据接收信号
void disconnected(); // 断开连接信号
void sendDataReady(); // 数据准备好发送信号
private slots:
void onConnected(); // 连接成功的槽函数
void onDisconnected(); // 断开连接的槽函数
void onReadyRead(); // 准备读取数据的槽函数
};
1.连接到服务器
connectToServer()
方法用于设置目标服务器的 IP 和端口,并启动线程:
该方法设置了目标服务器的 IP 和端口,并检查线程是否已经在运行。isRunning()
方法用于检测线程的状态,如果线程没有运行,则调用 start()
方法启动线程,start()
方法会内部调用 run()
方法。
void ClientThread::connectToServer(const QString& IP, quint16 port)
{
this->IP = IP;
this->port = port;
if (!isRunning())
{
start(); // 启动线程,调用run()方法
}
}
2.线程的核心:run()
方法
run()
方法是 QThread
的核心,它在新线程中执行所有代码。我们在这里实现了 TCP 连接的创建和管理:
-
创建 QTcpSocket 对象:在新线程中创建
QTcpSocket
对象。重要的是,QTcpSocket
必须在创建它的线程中使用,不能跨线程使用,因为QTcpSocket
不是线程安全的。 -
连接信号和槽:将
QTcpSocket
的connected
、disconnected
和readyRead
信号分别连接到ClientThread
的onConnected()
、onDisconnected()
和onReadyRead()
槽函数。这样,当这些事件发生时,ClientThread
会自动调用相应的槽函数进行处理。 -
连接到服务器:使用
connectToHost()
方法尝试连接到服务器,IP
和port
是之前在connectToServer()
方法中设置的。随后调用waitForConnected()
方法等待连接的建立。如果在 1000 毫秒内没有成功连接,则输出调试信息,并发射connectionResult(false)
信号通知连接失败。 -
处理发送数据等耗时操作:使用 write
()
方法进行发送数据 -
进入事件循环:调用
exec()
方法进入线程的事件循环。这一步是关键,它使线程进入一个无限循环,等待和处理事件(例如,网络数据的读取)。如果没有事件循环,线程会立即结束,QTcpSocket
的信号槽将无法正常工作。
3.停止线程:stop()
方法
stop()
方法用于安全地停止线程:
在 stop()
方法中,我们将 isStopped
设置为 true
,表示线程应停止运行。如果 client
(即 QTcpSocket
对象)存在,我们调用 disconnectFromHost()
方法断开与服务器的连接,并使用 waitForDisconnected()
方法等待连接完全断开。
void ClientThread::stop()
{
isStopped = true; // 设置停止标志
if (client)
{
client->disconnectFromHost(); // 断开与服务器的连接
client->waitForDisconnected(); // 等待连接完全断开
}
}
4.信号槽的实现
onConnected()
:当成功连接到服务器时调用,发射 connectionResult(true)
信号:
void ClientThread::onConnected()
{
emit connectionResult(true); // 发射连接成功信号
}
onDisconnected()
:当与服务器的连接断开时调用,发射 disconnected()
信号,并退出事件循环:
void ClientThread::onDisconnected()
{
emit disconnected(); // 发射断开连接信号
quit(); // 退出事件循环
}
onReadyRead()
:当有数据可以读取时调用。可以在此函数中添加数据处理逻辑:
void ClientThread::onReadyRead()
{
// 读取和处理数据的逻辑
QByteArray data = client->readAll();
// 这里可以添加对接收数据的处理
}
三、MainWindow
类的详细实现
1.初始化客户端线程
void MainWindow::on_sendData_clicked()
{
clientThread = new ClientThread(this); // 创建ClientThread实例
connect(clientThread, &ClientThread::connectionResult, this, &MainWindow::onConnectionResult); // 连接信号槽
connect(clientThread, &ClientThread::dataReceived, this, &MainWindow::onDataReceived);
connect(clientThread, &ClientThread::disconnected, this, &MainWindow::onDisconnected);
clientThread->addSendData(data); // 添加要发送的数据
clientThread->connectToServer(ip, port); // 连接到服务器
}
void MainWindow::onConnectionResult(bool success)
{
if (success) {
qDebug() << "连接成功";
}
else {
qDebug() << "连接失败";
QMessageBox::warning(this, "警告", "连接失败!!!");
}
}
void MainWindow::onDataReceived(int data)
{
qDebug() << "收到数据 << " << data;
}
void MainWindow::onDisconnected()
{
qDebug() << "已断开连接";
QMessageBox::warning(this, "警告", "已断开连接,请重新连接!!!");
}