第十八章:QT文件与网络——让程序与世界对话

说实话,QT 的文件和网络模块,是我当年从「单机小王子」进阶到「联网老司机」的关键一步。你想想看,一个程序如果只能读写自己的内存,那多没意思?能读写文件、能访问网络,这才算真正「活」过来了。

这一章,咱们就聊聊 QT 里怎么跟文件系统和网络打交道。我会把我在实际项目中踩过的坑、总结的经验,一股脑儿倒给你。

18.1 文件操作三剑客:QFile、QFileInfo、QDir

先说文件。QT 里操作文件,主要靠三个类:QFileQFileInfoQDir。它们各司其职,配合起来非常顺手。

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 知识体系总览

说了这么多,咱们用一张图来梳理一下本章的知识结构。这样你脑子里能有个清晰的框架。

QT 文件与网络 文件操作 QFile QFileInfo QDir 网络通信 QTcpSocket QUdpSocket HTTP 请求 QNetworkAccessManager 核心要点 • 文件操作:QFile 读写内容,QFileInfo 查属性,QDir 管目录 • TCP:面向连接、可靠,适合文件传输、聊天等场景 • UDP:无连接、速度快,适合广播、视频流等场景 • HTTP:QNetworkAccessManager 搞定 GET/POST,注意异步处理

18.5 避坑指南

最后,分享几个我当年踩过的坑,你遇到了能少走弯路。

  • 文件路径编码问题:Windows 下中文路径容易出乱码。我建议统一用 UTF-8 编码,QString 内部就是 UTF-16,但传给 QFile 时最好用 toLocal8Bit() 转一下。
  • TCP 粘包问题:TCP 是流式协议,没有消息边界。你发两次 write,对方可能一次 readyRead 就全收到了。解决办法就是自定义协议头,或者用固定长度的消息。
  • UDP 丢包不报错:UDP 丢包了,发送端和接收端都不会收到任何错误通知。所以如果你的应用对数据完整性要求高,还是老老实实用 TCP 吧。
  • HTTP 请求超时:QNetworkAccessManager 默认没有超时设置。我习惯用 QTimer::singleShot 配合 reply->abort() 来实现超时控制。

好了,文件与网络这块儿就聊到这儿。这些东西你多用几次就熟了。记住一点:文件操作要检查返回值,网络通信要处理好异步信号。这两条记住了,能省掉你不少调试时间。


公众号:蓝海资料掘金营,微信deep3321