第十八章:QT文件与网络——让程序与世界对话
说实话,QT 的文件和网络模块,是我当年从「单机小王子」进阶到「联网老司机」的关键一步。你想想看,一个程序如果只能读写自己的内存,那多没意思?能读写文件、能访问网络,这才算真正「活」过来了。
这一章,咱们就聊聊 QT 里怎么跟文件系统和网络打交道。我会把我在实际项目中踩过的坑、总结的经验,一股脑儿倒给你。
18.1 文件操作三剑客:QFile、QFileInfo、QDir
先说文件。QT 里操作文件,主要靠三个类:QFile、QFileInfo 和 QDir。它们各司其职,配合起来非常顺手。
18.1.1 QFile:读写文件内容
QFile 就是用来读写文件内容的。用法很简单,打开、读写、关闭。我习惯用 QTextStream 或者 QDataStream 来配合它,这样读写更优雅。
// 写文件
QFile file("data.txt");
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << "Hello, QT!" << "\n";
out << "这是第二行" << "\n";
file.close();
}
// 读文件
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
qDebug() << line;
}
file.close();
}
小提示:打开文件时一定要检查返回值。我在项目中遇到过因为路径不对、权限不足导致打开失败的情况,如果不检查,后面读写就会崩溃。
18.1.2 QFileInfo:获取文件信息
有时候你不想读写文件内容,只想看看这个文件多大、什么时候修改的、后缀名是什么。这时候 QFileInfo 就派上用场了。
QFileInfo info("data.txt");
if (info.exists()) {
qDebug() << "文件名:" << info.fileName();
qDebug() << "后缀:" << info.suffix();
qDebug() << "大小:" << info.size() << "字节";
qDebug() << "最后修改:" << info.lastModified().toString("yyyy-MM-dd hh:mm:ss");
qDebug() << "绝对路径:" << info.absoluteFilePath();
}
嗯,这里要注意:QFileInfo 在构造时不会立即去访问文件系统,它是在你调用具体方法(比如 exists())时才去获取信息。所以如果你改了文件,记得重新构造一个 QFileInfo 对象,或者调用 refresh() 方法。
18.1.3 QDir:目录操作
创建目录、遍历文件、切换路径——这些活儿都归 QDir 管。
// 创建目录
QDir dir;
if (!dir.exists("my_folder")) {
dir.mkdir("my_folder");
}
// 遍历目录下所有 .txt 文件
QDir targetDir("my_folder");
QStringList filters;
filters << "*.txt";
targetDir.setNameFilters(filters);
QFileInfoList fileList = targetDir.entryInfoList();
foreach (QFileInfo fi, fileList) {
qDebug() << fi.fileName();
}
// 获取当前路径
qDebug() << "当前路径:" << QDir::currentPath();
注意:跨平台时路径分隔符是个坑。Windows 用反斜杠 \,Linux/macOS 用正斜杠 /。QT 提供了 QDir::separator() 来获取当前系统的分隔符,但我个人建议统一用正斜杠 /,QT 会自动帮你转换。
18.2 网络通信:TCP 与 UDP
文件操作搞定了,咱们聊聊网络。QT 的网络模块封装得非常好,你不需要懂底层 socket 的细节,就能写出稳定的网络程序。
18.2.1 QTcpSocket:可靠的连接
TCP 是面向连接的、可靠的传输协议。说白了,就是两边先「握手」建立连接,然后保证数据按顺序到达。适合传输文件、聊天消息这种不能丢数据的场景。
我做过一个项目,需要把采集到的传感器数据实时上传到服务器。当时用的就是 QTcpSocket。核心代码大概长这样:
// 客户端连接服务器
QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost("192.168.1.100", 8080);
// 连接成功时触发
connect(socket, &QTcpSocket::connected, [=]() {
qDebug() << "连接成功!";
socket->write("Hello Server!");
});
// 收到数据时触发
connect(socket, &QTcpSocket::readyRead, [=]() {
QByteArray data = socket->readAll();
qDebug() << "收到:" << data;
});
// 断开连接时触发
connect(socket, &QTcpSocket::disconnected, [=]() {
qDebug() << "连接断开";
});
核心要点:TCP 通信一定要处理好 readyRead 信号。因为数据是分片到达的,一次 readyRead 可能只收到一部分数据。我习惯在应用层定义一个简单的协议头,比如前4个字节表示数据长度,这样就能准确判断数据是否收完整了。
18.2.2 QUdpSocket:轻量级的广播
UDP 就不一样了。它不需要建立连接,直接发数据包就行。速度快,但不保证到达,也不保证顺序。适合视频流、游戏状态同步这种丢几个包也无所谓的场景。
// UDP 发送端
QUdpSocket *udpSocket = new QUdpSocket(this);
QByteArray data = "Hello UDP!";
udpSocket->writeDatagram(data, QHostAddress::Broadcast, 8888);
// UDP 接收端
QUdpSocket *recvSocket = new QUdpSocket(this);
recvSocket->bind(8888, QUdpSocket::ShareAddress);
connect(recvSocket, &QUdpSocket::readyRead, [=]() {
while (recvSocket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(recvSocket->pendingDatagramSize());
recvSocket->readDatagram(datagram.data(), datagram.size());
qDebug() << "收到UDP:" << datagram;
}
});
我曾经用 UDP 做过一个局域网发现功能。设备启动时广播一个「我在这里」的消息,管理端收到后就能自动发现设备。这个方案简单又高效,比手动配置 IP 方便多了。
18.3 HTTP 请求:QNetworkAccessManager
现在大部分应用都要跟 Web API 打交道。QT 里做 HTTP 请求,主要用 QNetworkAccessManager。它支持 GET、POST、PUT、DELETE 等常见方法,而且是非阻塞的——不会卡界面。
// 创建网络管理器
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
// 发送 GET 请求
QNetworkReply *reply = manager->get(QNetworkRequest(QUrl("https://api.example.com/data")));
// 处理响应
connect(reply, &QNetworkReply::finished, [=]() {
if (reply->error() == QNetworkReply::NoError) {
QByteArray responseData = reply->readAll();
qDebug() << "响应内容:" << QString::fromUtf8(responseData);
} else {
qDebug() << "请求出错:" << reply->errorString();
}
reply->deleteLater();
});
// 发送 POST 请求(带 JSON 数据)
QNetworkRequest request(QUrl("https://api.example.com/login"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QJsonObject json;
json["username"] = "admin";
json["password"] = "123456";
QNetworkReply *postReply = manager->post(request, QJsonDocument(json).toJson());
经验之谈:处理 HTTP 响应时,别忘了调用 reply->deleteLater()。否则 QNetworkReply 对象会一直占用内存。另外,HTTPS 请求需要 OpenSSL 支持,部署时记得带上对应的 DLL 或 .so 文件。
18.4 知识体系总览
说了这么多,咱们用一张图来梳理一下本章的知识结构。这样你脑子里能有个清晰的框架。
18.5 避坑指南
最后,分享几个我当年踩过的坑,你遇到了能少走弯路。
- 文件路径编码问题:Windows 下中文路径容易出乱码。我建议统一用 UTF-8 编码,
QString内部就是 UTF-16,但传给QFile时最好用toLocal8Bit()转一下。 - TCP 粘包问题:TCP 是流式协议,没有消息边界。你发两次
write,对方可能一次readyRead就全收到了。解决办法就是自定义协议头,或者用固定长度的消息。 - UDP 丢包不报错:UDP 丢包了,发送端和接收端都不会收到任何错误通知。所以如果你的应用对数据完整性要求高,还是老老实实用 TCP 吧。
- HTTP 请求超时:
QNetworkAccessManager默认没有超时设置。我习惯用QTimer::singleShot配合reply->abort()来实现超时控制。
好了,文件与网络这块儿就聊到这儿。这些东西你多用几次就熟了。记住一点:文件操作要检查返回值,网络通信要处理好异步信号。这两条记住了,能省掉你不少调试时间。
公众号:蓝海资料掘金营,微信deep3321