1. 课程导学与环境搭建:Qt网络与多线程编程全景图
各位同学,欢迎来到这门实战课。我是你们的老朋友,一个在Qt坑里摸爬滚打了十几年的工程师。
开篇第一讲,咱们不急着写代码。先花点时间,把整个知识地图铺开看看。你想想看,网络通信、多线程、性能调优——这三块东西单独拎出来,每一块都够写一本书。但实际项目中,它们往往是搅在一起的。我见过太多人,单线程跑得飞起,一上多线程就崩;TCP收发没问题,一压并发就丢包。
这门课的目标,就是帮你把这些「搅在一起」的东西,拆清楚、讲明白、练扎实。
1.1 全景图:Qt网络与多线程到底在解决什么问题?
先看一张图,把整个课程的知识体系串起来。
这张图,说白了就是整个课程的骨架。从上往下看:你的应用在最上面,下面是网络和多线程两大支柱,再往下是性能调优,最底层是Qt的事件循环和操作系统的I/O机制。
我个人习惯把这种结构叫做「分层解耦」——每一层只关心自己的事,通过信号槽或者回调来沟通。这样做的好处是,哪一层出了问题,你一眼就能定位。
1.2 开发环境配置:Qt 6 + CMake
好,地图看完了,咱们开始搭环境。我建议你直接用 Qt 6.5 LTS 或更高版本,配合 CMake 3.22+。为什么不用qmake?嗯,Qt官方自己都在往CMake迁移,咱们就别逆势而为了。
1.2.1 安装清单
| 组件 | 版本要求 | 备注 |
|---|---|---|
| Qt框架 | 6.5 LTS / 6.6+ | 建议在线安装,勾选Qt Charts(后续性能可视化用) |
| CMake | 3.22+ | Windows下注意把cmake.exe加入PATH |
| 编译器 | MSVC 2022 / GCC 11+ / Clang 14+ | Linux用GCC,macOS用Clang,Windows用MSVC |
| 构建工具 | Ninja 或 Make | Ninja在Windows上更快,我推荐 |
1.2.2 第一个CMake工程文件
咱们直接上手。创建一个新目录,比如 QtNetCourse,在里面放一个 CMakeLists.txt:
cmake_minimum_required(VERSION 3.22)
project(QtNetCourse VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 自动找到Qt6的组件
find_package(Qt6 REQUIRED COMPONENTS Core Network Widgets)
# 添加可执行文件
add_executable(${PROJECT_NAME}
main.cpp
tcpserver.h
tcpserver.cpp
tcpclient.h
tcpclient.cpp
)
# 链接Qt库
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt6::Core
Qt6::Network
Qt6::Widgets
)
这里有个细节:find_package 里的 REQUIRED 关键字,意思是「找不到Qt6就直接报错,别磨叽」。我刚开始用CMake时,经常忘了加这个,结果编译时一堆莫名其妙的undefined reference,查半天才发现是Qt没找到。
1.3 第一个TCP通信Demo:从零开始跑通
环境搭好了,咱们写一个最简单的TCP回显服务器和客户端。这个Demo虽然简单,但包含了网络通信的完整骨架:连接、收发、断开。
1.3.1 服务器端代码
先看服务器。它监听一个端口,收到客户端消息后原样返回。
// tcpserver.h
#pragma once
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
class TcpServer : public QObject
{
Q_OBJECT
public:
explicit TcpServer(QObject *parent = nullptr);
bool start(quint16 port);
private slots:
void onNewConnection();
void onReadyRead();
void onClientDisconnected();
private:
QTcpServer *m_server;
};
// tcpserver.cpp
#include "tcpserver.h"
#include <QDebug>
TcpServer::TcpServer(QObject *parent)
: QObject(parent)
, m_server(new QTcpServer(this))
{
// 新连接进来时触发
connect(m_server, &QTcpServer::newConnection,
this, &TcpServer::onNewConnection);
}
bool TcpServer::start(quint16 port)
{
if (!m_server->listen(QHostAddress::Any, port)) {
qCritical() << "服务器启动失败:" << m_server->errorString();
return false;
}
qInfo() << "服务器已启动,监听端口:" << port;
return true;
}
void TcpServer::onNewConnection()
{
QTcpSocket *client = m_server->nextPendingConnection();
connect(client, &QTcpSocket::readyRead,
this, &TcpServer::onReadyRead);
connect(client, &QTcpSocket::disconnected,
this, &TcpServer::onClientDisconnected);
qInfo() << "新客户端连接:" << client->peerAddress().toString();
}
void TcpServer::onReadyRead()
{
QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
if (!client) return;
QByteArray data = client->readAll();
qInfo() << "收到数据:" << data;
// 回显:原样发回去
client->write(data);
}
void TcpServer::onClientDisconnected()
{
QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
if (client) {
qInfo() << "客户端断开:" << client->peerAddress().toString();
client->deleteLater();
}
}
onReadyRead 里我用的是 readAll()。你可能会问:如果数据没发完怎么办?其实Qt的TCP底层已经帮你做了缓冲,readyRead 信号来一次,你就把当前缓冲区里的数据全部读走。但要注意,TCP是流协议,没有消息边界。后面我们会专门讲怎么处理粘包和半包问题。
1.3.2 客户端代码
客户端就简单了:连接服务器,发一条消息,然后等回显。
// tcpclient.h
#pragma once
#include <QObject>
#include <QTcpSocket>
class TcpClient : public QObject
{
Q_OBJECT
public:
explicit TcpClient(QObject *parent = nullptr);
void connectToServer(const QString &host, quint16 port);
void sendMessage(const QString &msg);
private slots:
void onConnected();
void onReadyRead();
void onErrorOccurred(QAbstractSocket::SocketError error);
private:
QTcpSocket *m_socket;
};
// tcpclient.cpp
#include "tcpclient.h"
#include <QDebug>
TcpClient::TcpClient(QObject *parent)
: QObject(parent)
, m_socket(new QTcpSocket(this))
{
connect(m_socket, &QTcpSocket::connected,
this, &TcpClient::onConnected);
connect(m_socket, &QTcpSocket::readyRead,
this, &TcpClient::onReadyRead);
connect(m_socket, &QTcpSocket::errorOccurred,
this, &TcpClient::onErrorOccurred);
}
void TcpClient::connectToServer(const QString &host, quint16 port)
{
qInfo() << "正在连接服务器:" << host << port;
m_socket->connectToHost(host, port);
}
void TcpClient::sendMessage(const QString &msg)
{
if (m_socket->state() == QAbstractSocket::ConnectedState) {
m_socket->write(msg.toUtf8());
m_socket->flush(); // 立即发送,不等待事件循环
}
}
void TcpClient::onConnected()
{
qInfo() << "连接成功!";
sendMessage("Hello, Qt Server!");
}
void TcpClient::onReadyRead()
{
QByteArray response = m_socket->readAll();
qInfo() << "收到回显:" << response;
}
void TcpClient::onErrorOccurred(QAbstractSocket::SocketError error)
{
Q_UNUSED(error)
qCritical() << "连接错误:" << m_socket->errorString();
}
1.3.3 main.cpp:把一切串起来
#include <QCoreApplication>
#include "tcpserver.h"
#include "tcpclient.h"
#include <QTimer>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// 启动服务器
TcpServer server;
if (!server.start(12345)) {
return -1;
}
// 创建客户端,延迟1秒连接(确保服务器已就绪)
TcpClient client;
QTimer::singleShot(1000, [&client]() {
client.connectToServer("127.0.0.1", 12345);
});
return app.exec();
}
服务器已启动,监听端口: 12345正在连接服务器: 127.0.0.1 12345新客户端连接: "127.0.0.1"连接成功!收到数据: "Hello, Qt Server!"收到回显: "Hello, Qt Server!"
1.4 本章小结:你学到了什么?
这一章,我们做了三件事:
- 画了全景图——知道了Qt网络和多线程编程的完整知识结构,心里有底了。
- 搭了开发环境——Qt 6 + CMake,Windows用户记得用MSVC。
- 跑通了第一个TCP Demo——服务器回显,客户端收发,完整的信号槽流程。
说实话,这个Demo在生产环境里肯定不够用。它没有处理粘包,没有做断线重连,更没有考虑多线程并发。但别急,这些坑我们后面一个一个填。