Qt网络编程详解(下):项目实战

📋 第六章:网络编程最佳实践

本章总结Qt网络编程中的常见问题和最佳解决方案,帮助你写出健壮、高效的网络应用。


6.1 错误处理

6.1.1 网络错误分类
cpp 复制代码
// QNetworkReply::NetworkError 常见错误码
void handleNetworkError(QNetworkReply::NetworkError error) {
    switch (error) {
    // 连接错误
    case QNetworkReply::ConnectionRefusedError:
        qDebug() << "❌ 连接被拒绝";
        break;
    case QNetworkReply::RemoteHostClosedError:
        qDebug() << "❌ 远程主机关闭连接";
        break;
    case QNetworkReply::HostNotFoundError:
        qDebug() << "❌ 找不到主机";
        break;
    case QNetworkReply::TimeoutError:
        qDebug() << "❌ 连接超时";
        break;
        
    // SSL错误
    case QNetworkReply::SslHandshakeFailedError:
        qDebug() << "❌ SSL握手失败";
        break;
        
    // HTTP错误
    case QNetworkReply::ContentNotFoundError:
        qDebug() << "❌ 404 - 资源不存在";
        break;
    case QNetworkReply::AuthenticationRequiredError:
        qDebug() << "❌ 需要认证";
        break;
    case QNetworkReply::ContentAccessDenied:
        qDebug() << "❌ 403 - 访问被拒绝";
        break;
        
    // 其他
    case QNetworkReply::UnknownNetworkError:
        qDebug() << "❌ 未知网络错误";
        break;
    default:
        qDebug() << "❌ 错误码:" << error;
    }
}
6.1.2 统一错误处理类
cpp 复制代码
class NetworkErrorHandler {
public:
    struct ErrorInfo {
        int httpStatus;
        QString code;
        QString message;
        bool retryable;
    };
    
    static ErrorInfo analyze(QNetworkReply *reply) {
        ErrorInfo info;
        info.httpStatus = reply->attribute(
            QNetworkRequest::HttpStatusCodeAttribute).toInt();
        
        QNetworkReply::NetworkError err = reply->error();
        
        // 可重试的错误
        info.retryable = (err == QNetworkReply::TimeoutError ||
                          err == QNetworkReply::TemporaryNetworkFailureError ||
                          info.httpStatus == 502 ||
                          info.httpStatus == 503 ||
                          info.httpStatus == 504);
        
        // 错误分类
        if (err == QNetworkReply::NoError) {
            info.code = "SUCCESS";
            info.message = "成功";
        } else if (info.httpStatus >= 400 && info.httpStatus < 500) {
            info.code = "CLIENT_ERROR";
            info.message = getClientErrorMessage(info.httpStatus, reply);
        } else if (info.httpStatus >= 500) {
            info.code = "SERVER_ERROR";
            info.message = "服务器错误,请稍后重试";
        } else if (err == QNetworkReply::TimeoutError) {
            info.code = "TIMEOUT";
            info.message = "请求超时,请检查网络";
        } else if (err == QNetworkReply::HostNotFoundError) {
            info.code = "DNS_ERROR";
            info.message = "无法连接服务器,请检查网络";
        } else {
            info.code = "NETWORK_ERROR";
            info.message = reply->errorString();
        }
        
        return info;
    }
    
private:
    static QString getClientErrorMessage(int status, QNetworkReply *reply) {
        // 尝试解析服务器返回的错误信息
        QByteArray body = reply->readAll();
        QJsonDocument doc = QJsonDocument::fromJson(body);
        if (doc.isObject() && doc.object().contains("message")) {
            return doc.object()["message"].toString();
        }
        
        // 默认消息
        switch (status) {
        case 400: return "请求参数错误";
        case 401: return "未登录或登录已过期";
        case 403: return "没有访问权限";
        case 404: return "请求的资源不存在";
        case 429: return "请求太频繁,请稍后再试";
        default: return QString("请求失败(%1)").arg(status);
        }
    }
};

6.2 超时设置

6.2.1 请求超时配置
cpp 复制代码
// Qt 5.15+ 推荐方式
void configureTimeout(QNetworkRequest &request) {
    // 设置传输超时(毫秒)
    request.setTransferTimeout(30000);  // 30秒
}

// 兼容旧版本的方式
class TimeoutNetworkManager : public QObject {
    Q_OBJECT
public:
    TimeoutNetworkManager(int timeoutMs = 30000, QObject *parent = nullptr)
        : QObject(parent), timeout(timeoutMs) {
        manager = new QNetworkAccessManager(this);
    }
    
    QNetworkReply* get(const QNetworkRequest &request) {
        QNetworkReply *reply = manager->get(request);
        setupTimeout(reply);
        return reply;
    }
    
    QNetworkReply* post(const QNetworkRequest &request, const QByteArray &data) {
        QNetworkReply *reply = manager->post(request, data);
        setupTimeout(reply);
        return reply;
    }
    
private:
    void setupTimeout(QNetworkReply *reply) {
        QTimer *timer = new QTimer(reply);
        timer->setSingleShot(true);
        
        connect(timer, &QTimer::timeout, reply, [reply]() {
            if (reply->isRunning()) {
                reply->abort();
                qDebug() << "⏰ 请求超时,已中止";
            }
        });
        
        connect(reply, &QNetworkReply::finished, timer, &QTimer::stop);
        
        timer->start(timeout);
    }
    
    QNetworkAccessManager *manager;
    int timeout;
};
6.2.2 分阶段超时
cpp 复制代码
class PhaseTimeoutRequest : public QObject {
    Q_OBJECT
public:
    // 连接超时、读取超时、总超时
    void setTimeouts(int connectMs, int readMs, int totalMs) {
        connectTimeout = connectMs;
        readTimeout = readMs;
        totalTimeout = totalMs;
    }
    
    void get(const QUrl &url) {
        QNetworkRequest request(url);
        reply = manager->get(request);
        
        // 总超时
        totalTimer = new QTimer(this);
        totalTimer->setSingleShot(true);
        connect(totalTimer, &QTimer::timeout, this, &PhaseTimeoutRequest::onTotalTimeout);
        totalTimer->start(totalTimeout);
        
        // 连接超时(等待首个数据)
        connectTimer = new QTimer(this);
        connectTimer->setSingleShot(true);
        connect(connectTimer, &QTimer::timeout, this, &PhaseTimeoutRequest::onConnectTimeout);
        connectTimer->start(connectTimeout);
        
        // 读取超时(连接后)
        readTimer = new QTimer(this);
        readTimer->setSingleShot(true);
        
        connect(reply, &QNetworkReply::readyRead, this, [this]() {
            connectTimer->stop();  // 有数据了,停止连接超时
            readTimer->start(readTimeout);  // 重置读取超时
        });
        
        connect(reply, &QNetworkReply::finished, this, [this]() {
            totalTimer->stop();
            connectTimer->stop();
            readTimer->stop();
            emit finished(reply);
        });
    }
    
signals:
    void finished(QNetworkReply *reply);
    void timeout(const QString &phase);
    
private slots:
    void onConnectTimeout() {
        qDebug() << "⏰ 连接超时";
        reply->abort();
        emit timeout("connect");
    }
    
    void onTotalTimeout() {
        qDebug() << "⏰ 总超时";
        reply->abort();
        emit timeout("total");
    }
    
private:
    QNetworkAccessManager *manager = new QNetworkAccessManager(this);
    QNetworkReply *reply = nullptr;
    QTimer *connectTimer = nullptr;
    QTimer *readTimer = nullptr;
    QTimer *totalTimer = nullptr;
    int connectTimeout = 10000;
    int readTimeout = 30000;
    int totalTimeout = 60000;
};

6.3 重试机制

6.3.1 指数退避重试
cpp 复制代码
class RetryableRequest : public QObject {
    Q_OBJECT
public:
    RetryableRequest(QObject *parent = nullptr) : QObject(parent) {
        manager = new QNetworkAccessManager(this);
    }
    
    void setRetryPolicy(int maxRetries, int baseDelayMs) {
        this->maxRetries = maxRetries;
        this->baseDelay = baseDelayMs;
    }
    
    void get(const QUrl &url) {
        currentUrl = url;
        retryCount = 0;
        sendRequest();
    }
    
signals:
    void success(const QByteArray &data);
    void failure(const QString &error);
    void retrying(int attempt, int delayMs);
    
private:
    void sendRequest() {
        QNetworkRequest request(currentUrl);
        request.setTransferTimeout(15000);
        
        QNetworkReply *reply = manager->get(request);
        connect(reply, &QNetworkReply::finished, this, [this, reply]() {
            handleResponse(reply);
        });
    }
    
    void handleResponse(QNetworkReply *reply) {
        // 检查是否可重试
        auto errorInfo = NetworkErrorHandler::analyze(reply);
        
        if (reply->error() == QNetworkReply::NoError) {
            emit success(reply->readAll());
        } else if (errorInfo.retryable && retryCount < maxRetries) {
            // 计算延迟:指数退避 + 随机抖动
            int delay = baseDelay * qPow(2, retryCount);
            delay += QRandomGenerator::global()->bounded(1000);  // 0-1秒抖动
            
            retryCount++;
            qDebug() << QString("🔄 第%1次重试,%2ms后执行")
                        .arg(retryCount).arg(delay);
            
            emit retrying(retryCount, delay);
            
            QTimer::singleShot(delay, this, &RetryableRequest::sendRequest);
        } else {
            emit failure(errorInfo.message);
        }
        
        reply->deleteLater();
    }
    
    QNetworkAccessManager *manager;
    QUrl currentUrl;
    int retryCount = 0;
    int maxRetries = 3;
    int baseDelay = 1000;  // 1秒
};

// 使用示例
void testRetry() {
    RetryableRequest *req = new RetryableRequest();
    req->setRetryPolicy(3, 1000);  // 最多3次,初始延迟1秒
    
    QObject::connect(req, &RetryableRequest::retrying,
        [](int attempt, int delay) {
            qDebug() << "第" << attempt << "次重试," << delay << "ms后";
        });
    
    QObject::connect(req, &RetryableRequest::success,
        [](const QByteArray &data) {
            qDebug() << "成功:" << data.left(100);
        });
    
    req->get(QUrl("https://api.example.com/data"));
}

6.4 性能优化

6.4.1 连接复用
cpp 复制代码
// ✅ 正确:使用单例管理器
class HttpManager {
public:
    static QNetworkAccessManager* instance() {
        static QNetworkAccessManager manager;
        return &manager;
    }
};

// 使用
QNetworkReply *reply = HttpManager::instance()->get(request);

// ❌ 错误:每次请求创建新管理器(会创建新连接)
void badExample() {
    QNetworkAccessManager *manager = new QNetworkAccessManager();
    manager->get(request);  // 无法复用连接
}
6.4.2 请求合并
cpp 复制代码
class BatchRequest : public QObject {
    Q_OBJECT
public:
    BatchRequest(int batchSize = 10, int intervalMs = 100, QObject *parent = nullptr)
        : QObject(parent), batchSize(batchSize) {
        
        manager = new QNetworkAccessManager(this);
        
        batchTimer = new QTimer(this);
        connect(batchTimer, &QTimer::timeout, this, &BatchRequest::flush);
        batchTimer->start(intervalMs);
    }
    
    void enqueue(const QString &id, const QUrl &url) {
        pendingRequests.append({id, url});
        
        if (pendingRequests.size() >= batchSize) {
            flush();
        }
    }
    
signals:
    void completed(const QString &id, const QByteArray &data);
    
private slots:
    void flush() {
        if (pendingRequests.isEmpty()) return;
        
        // 批量发送
        auto batch = pendingRequests.mid(0, batchSize);
        pendingRequests = pendingRequests.mid(batchSize);
        
        for (const auto &req : batch) {
            QNetworkReply *reply = manager->get(QNetworkRequest(req.url));
            QString id = req.id;
            
            connect(reply, &QNetworkReply::finished, this, [this, reply, id]() {
                emit completed(id, reply->readAll());
                reply->deleteLater();
            });
        }
    }
    
private:
    struct PendingRequest {
        QString id;
        QUrl url;
    };
    
    QNetworkAccessManager *manager;
    QList<PendingRequest> pendingRequests;
    QTimer *batchTimer;
    int batchSize;
};
6.4.3 响应缓存
cpp 复制代码
class CachedNetworkManager : public QObject {
    Q_OBJECT
public:
    CachedNetworkManager(QObject *parent = nullptr) : QObject(parent) {
        manager = new QNetworkAccessManager(this);
        
        // 设置磁盘缓存
        cache = new QNetworkDiskCache(this);
        cache->setCacheDirectory(QStandardPaths::writableLocation(
            QStandardPaths::CacheLocation) + "/http_cache");
        cache->setMaximumCacheSize(50 * 1024 * 1024);  // 50MB
        
        manager->setCache(cache);
    }
    
    void get(const QUrl &url, bool forceRefresh = false) {
        QNetworkRequest request(url);
        
        if (forceRefresh) {
            // 强制刷新,不使用缓存
            request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
                                 QNetworkRequest::AlwaysNetwork);
        } else {
            // 优先使用缓存
            request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
                                 QNetworkRequest::PreferCache);
        }
        
        QNetworkReply *reply = manager->get(request);
        
        connect(reply, &QNetworkReply::finished, this, [this, reply]() {
            bool fromCache = reply->attribute(
                QNetworkRequest::SourceIsFromCacheAttribute).toBool();
            qDebug() << "数据来源:" << (fromCache ? "缓存" : "网络");
            
            emit dataReceived(reply->readAll(), fromCache);
            reply->deleteLater();
        });
    }
    
signals:
    void dataReceived(const QByteArray &data, bool fromCache);
    
private:
    QNetworkAccessManager *manager;
    QNetworkDiskCache *cache;
};

6.5 安全性

6.5.1 敏感数据保护
cpp 复制代码
// ❌ 错误:明文传输敏感数据
void badLogin(const QString &username, const QString &password) {
    QUrl url("http://api.example.com/login");  // HTTP!
    // 密码明文传输...
}

// ✅ 正确:使用HTTPS
void secureLogin(const QString &username, const QString &password) {
    QNetworkRequest request(QUrl("https://api.example.com/login"));
    
    // 设置SSL配置
    QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
    sslConfig.setProtocol(QSsl::TlsV1_2OrLater);
    request.setSslConfiguration(sslConfig);
    
    // 使用POST请求,密码不出现在URL中
    QJsonObject body;
    body["username"] = username;
    body["password"] = password;  // 通过HTTPS加密传输
    
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
    manager->post(request, QJsonDocument(body).toJson());
}
6.5.2 Token安全存储
cpp 复制代码
#include <QKeychain/Keychain>  // 使用QtKeychain库

class SecureTokenStorage {
public:
    // 安全存储Token
    static void saveToken(const QString &token) {
        QKeychain::WritePasswordJob job("MyApp");
        job.setKey("auth_token");
        job.setTextData(token);
        
        QEventLoop loop;
        QObject::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
        job.start();
        loop.exec();
        
        if (job.error()) {
            qDebug() << "保存Token失败:" << job.errorString();
        }
    }
    
    // 读取Token
    static QString loadToken() {
        QKeychain::ReadPasswordJob job("MyApp");
        job.setKey("auth_token");
        
        QEventLoop loop;
        QObject::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
        job.start();
        loop.exec();
        
        if (job.error()) {
            return QString();
        }
        return job.textData();
    }
    
    // 删除Token
    static void deleteToken() {
        QKeychain::DeletePasswordJob job("MyApp");
        job.setKey("auth_token");
        
        QEventLoop loop;
        QObject::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
        job.start();
        loop.exec();
    }
};
6.5.3 防止常见攻击
cpp 复制代码
class SecurityHelper {
public:
    // URL参数编码,防止注入
    static QString encodeParam(const QString &value) {
        return QUrl::toPercentEncoding(value);
    }
    
    // 验证URL,防止SSRF
    static bool isValidUrl(const QUrl &url) {
        // 只允许https
        if (url.scheme() != "https") {
            return false;
        }
        
        // 黑名单检查(禁止内网地址)
        QString host = url.host();
        if (host == "localhost" || 
            host.startsWith("127.") ||
            host.startsWith("192.168.") ||
            host.startsWith("10.") ||
            host.startsWith("172.16.")) {
            return false;
        }
        
        // 白名单检查
        static QStringList allowedDomains = {
            "api.example.com",
            "cdn.example.com"
        };
        
        for (const QString &domain : allowedDomains) {
            if (host == domain || host.endsWith("." + domain)) {
                return true;
            }
        }
        
        return false;
    }
    
    // 生成请求签名
    static QString signRequest(const QString &method, 
                               const QString &path,
                               const QString &timestamp,
                               const QString &secretKey) {
        QString payload = method + "\n" + path + "\n" + timestamp;
        
        QByteArray signature = QMessageAuthenticationCode::hash(
            payload.toUtf8(),
            secretKey.toUtf8(),
            QCryptographicHash::Sha256
        );
        
        return signature.toBase64();
    }
};

6.6 调试与日志

6.6.1 网络请求日志
cpp 复制代码
class NetworkLogger : public QObject {
    Q_OBJECT
public:
    static void install(QNetworkAccessManager *manager) {
        connect(manager, &QNetworkAccessManager::finished,
            [](QNetworkReply *reply) {
                logResponse(reply);
            });
    }
    
private:
    static void logResponse(QNetworkReply *reply) {
        QString method = reply->request().attribute(
            QNetworkRequest::CustomVerbAttribute).toString();
        if (method.isEmpty()) method = "GET";
        
        int status = reply->attribute(
            QNetworkRequest::HttpStatusCodeAttribute).toInt();
        
        QString url = reply->url().toString();
        qint64 elapsed = reply->request().attribute(
            QNetworkRequest::User).toLongLong();  // 如果设置了
        
        QString log = QString("[HTTP] %1 %2 -> %3")
            .arg(method)
            .arg(url)
            .arg(status);
        
        if (reply->error() != QNetworkReply::NoError) {
            qWarning() << log << "ERROR:" << reply->errorString();
        } else {
            qDebug() << log;
        }
    }
};

// 使用
NetworkLogger::install(HttpManager::instance());

第六章小结

本章总结了Qt网络编程的最佳实践:

主题 要点
错误处理 分类处理、友好提示、错误分析类
超时设置 连接/读取/总超时分阶段
重试机制 指数退避、最大次数、可重试判断
性能优化 连接复用、请求合并、响应缓存
安全性 HTTPS、Token安全存储、防注入
调试日志 请求/响应日志记录

核心原则

cpp 复制代码
// 1. 始终使用异步请求
connect(reply, &QNetworkReply::finished, ...);  // ✅

// 2. 始终处理错误
if (reply->error() != QNetworkReply::NoError) { ... }

// 3. 始终释放Reply
reply->deleteLater();

// 4. 使用单例管理器
HttpManager::instance()->get(request);

// 5. 设置合理超时
request.setTransferTimeout(30000);

// 6. 实现重试逻辑
if (isRetryable(error)) { retry(); }

📋 第七章:实战项目

本章提供多个完整可执行的网络编程实战项目,每个项目都包含完整的源代码和项目配置。


7.1 项目一:简易TCP聊天程序

7.1.1 项目概述

一个基于TCP的简易聊天程序,包含服务端和客户端,支持多客户端同时连接和消息广播。

功能特点

  • 服务端支持多客户端连接
  • 消息广播给所有在线用户
  • 用户上线/下线通知
  • 简洁的命令行界面
7.1.2 项目结构
复制代码
ChatDemo/
├── ChatServer/
│   ├── ChatServer.pro
│   ├── main.cpp
│   ├── chatserver.h
│   └── chatserver.cpp
└── ChatClient/
    ├── ChatClient.pro
    ├── main.cpp
    ├── chatclient.h
    └── chatclient.cpp
7.1.3 服务端完整代码

ChatServer.pro

qmake 复制代码
QT += core network
QT -= gui

CONFIG += c++17 console
CONFIG -= app_bundle

TARGET = ChatServer
TEMPLATE = app

SOURCES += main.cpp chatserver.cpp
HEADERS += chatserver.h

chatserver.h

cpp 复制代码
#ifndef CHATSERVER_H
#define CHATSERVER_H

#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QMap>
#include <QDateTime>

class ChatServer : public QObject
{
    Q_OBJECT
public:
    explicit ChatServer(quint16 port, QObject *parent = nullptr);
    bool start();
    void stop();

private slots:
    void onNewConnection();
    void onClientDisconnected();
    void onReadyRead();

private:
    void broadcast(const QString &message, QTcpSocket *excludeClient = nullptr);
    void sendToClient(QTcpSocket *client, const QString &message);
    QString formatMessage(const QString &sender, const QString &content);

    QTcpServer *m_server;
    QMap<QTcpSocket*, QString> m_clients;  // socket -> 用户名
    quint16 m_port;
};

#endif // CHATSERVER_H

chatserver.cpp

cpp 复制代码
#include "chatserver.h"
#include <QDebug>
#include <QDataStream>

ChatServer::ChatServer(quint16 port, QObject *parent)
    : QObject(parent), m_port(port)
{
    m_server = new QTcpServer(this);
    connect(m_server, &QTcpServer::newConnection, 
            this, &ChatServer::onNewConnection);
}

bool ChatServer::start()
{
    if (m_server->listen(QHostAddress::Any, m_port)) {
        qDebug() << "========================================";
        qDebug() << "  聊天服务器已启动";
        qDebug() << "  监听端口:" << m_port;
        qDebug() << "========================================";
        return true;
    }
    
    qDebug() << "服务器启动失败:" << m_server->errorString();
    return false;
}

void ChatServer::stop()
{
    // 关闭所有客户端连接
    for (QTcpSocket *client : m_clients.keys()) {
        client->disconnectFromHost();
    }
    m_clients.clear();
    m_server->close();
    qDebug() << "服务器已停止";
}

void ChatServer::onNewConnection()
{
    QTcpSocket *client = m_server->nextPendingConnection();
    
    connect(client, &QTcpSocket::disconnected, 
            this, &ChatServer::onClientDisconnected);
    connect(client, &QTcpSocket::readyRead, 
            this, &ChatServer::onReadyRead);
    
    // 暂时用地址作为临时标识
    QString tempName = QString("user_%1").arg(client->peerPort());
    m_clients[client] = tempName;
    
    qDebug() << "[连接]" << client->peerAddress().toString() 
             << ":" << client->peerPort();
    
    // 发送欢迎消息
    sendToClient(client, "[系统] 欢迎加入聊天室!请发送 /name 你的昵称 设置昵称");
    
    // 通知其他人
    broadcast(QString("[系统] %1 加入了聊天室").arg(tempName), client);
}

void ChatServer::onClientDisconnected()
{
    QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
    if (!client) return;
    
    QString username = m_clients.take(client);
    
    qDebug() << "[断开]" << username;
    
    // 通知其他人
    broadcast(QString("[系统] %1 离开了聊天室").arg(username));
    
    client->deleteLater();
}

void ChatServer::onReadyRead()
{
    QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
    if (!client) return;
    
    // 读取所有可用数据
    QByteArray data = client->readAll();
    QString message = QString::fromUtf8(data).trimmed();
    
    if (message.isEmpty()) return;
    
    QString username = m_clients.value(client);
    
    // 处理命令
    if (message.startsWith("/name ")) {
        QString newName = message.mid(6).trimmed();
        if (!newName.isEmpty() && newName.length() <= 20) {
            QString oldName = username;
            m_clients[client] = newName;
            broadcast(QString("[系统] %1 更名为 %2").arg(oldName, newName));
            qDebug() << "[更名]" << oldName << "->" << newName;
        } else {
            sendToClient(client, "[系统] 昵称无效(1-20个字符)");
        }
    }
    else if (message == "/list") {
        // 显示在线用户列表
        QStringList users;
        for (const QString &name : m_clients.values()) {
            users << name;
        }
        sendToClient(client, QString("[系统] 在线用户(%1): %2")
                     .arg(users.size()).arg(users.join(", ")));
    }
    else if (message == "/help") {
        sendToClient(client, "[帮助] 可用命令:\n"
                             "  /name <昵称> - 设置昵称\n"
                             "  /list - 查看在线用户\n"
                             "  /quit - 退出聊天室\n"
                             "  直接输入文字发送消息");
    }
    else if (message == "/quit") {
        client->disconnectFromHost();
    }
    else {
        // 普通聊天消息,广播给所有人
        QString formattedMsg = formatMessage(username, message);
        broadcast(formattedMsg);
        qDebug() << "[消息]" << username << ":" << message;
    }
}

void ChatServer::broadcast(const QString &message, QTcpSocket *excludeClient)
{
    QByteArray data = (message + "\n").toUtf8();
    
    for (QTcpSocket *client : m_clients.keys()) {
        if (client != excludeClient && client->state() == QAbstractSocket::ConnectedState) {
            client->write(data);
            client->flush();
        }
    }
}

void ChatServer::sendToClient(QTcpSocket *client, const QString &message)
{
    if (client && client->state() == QAbstractSocket::ConnectedState) {
        client->write((message + "\n").toUtf8());
        client->flush();
    }
}

QString ChatServer::formatMessage(const QString &sender, const QString &content)
{
    QString time = QDateTime::currentDateTime().toString("hh:mm:ss");
    return QString("[%1] %2: %3").arg(time, sender, content);
}

main.cpp(服务端)

cpp 复制代码
#include <QCoreApplication>
#include "chatserver.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    quint16 port = 9999;
    if (argc > 1) {
        port = QString(argv[1]).toUShort();
    }
    
    ChatServer server(port);
    if (!server.start()) {
        return 1;
    }
    
    return a.exec();
}
7.1.4 客户端完整代码

ChatClient.pro

qmake 复制代码
QT += core network
QT -= gui

CONFIG += c++17 console
CONFIG -= app_bundle

TARGET = ChatClient
TEMPLATE = app

SOURCES += main.cpp chatclient.cpp
HEADERS += chatclient.h

chatclient.h

cpp 复制代码
#ifndef CHATCLIENT_H
#define CHATCLIENT_H

#include <QObject>
#include <QTcpSocket>
#include <QTextStream>
#include <QSocketNotifier>

class ChatClient : public QObject
{
    Q_OBJECT
public:
    explicit ChatClient(const QString &host, quint16 port, QObject *parent = nullptr);
    void connectToServer();

private slots:
    void onConnected();
    void onDisconnected();
    void onReadyRead();
    void onError(QAbstractSocket::SocketError error);
    void onStdinReady();

private:
    void sendMessage(const QString &message);
    
    QTcpSocket *m_socket;
    QString m_host;
    quint16 m_port;
    QSocketNotifier *m_stdinNotifier;
    QTextStream m_stdin;
};

#endif // CHATCLIENT_H

chatclient.cpp

cpp 复制代码
#include "chatclient.h"
#include <QDebug>
#include <QCoreApplication>

#ifdef Q_OS_WIN
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#endif

ChatClient::ChatClient(const QString &host, quint16 port, QObject *parent)
    : QObject(parent), m_host(host), m_port(port), m_stdin(stdin)
{
    m_socket = new QTcpSocket(this);
    
    connect(m_socket, &QTcpSocket::connected, 
            this, &ChatClient::onConnected);
    connect(m_socket, &QTcpSocket::disconnected, 
            this, &ChatClient::onDisconnected);
    connect(m_socket, &QTcpSocket::readyRead, 
            this, &ChatClient::onReadyRead);
    connect(m_socket, &QTcpSocket::errorOccurred, 
            this, &ChatClient::onError);
    
    // 设置标准输入监听
#ifdef Q_OS_WIN
    // Windows下需要特殊处理
    _setmode(_fileno(stdin), _O_TEXT);
#endif
    
    m_stdinNotifier = new QSocketNotifier(fileno(stdin), 
                                          QSocketNotifier::Read, this);
    connect(m_stdinNotifier, &QSocketNotifier::activated, 
            this, &ChatClient::onStdinReady);
}

void ChatClient::connectToServer()
{
    qDebug() << "正在连接服务器" << m_host << ":" << m_port << "...";
    m_socket->connectToHost(m_host, m_port);
}

void ChatClient::onConnected()
{
    qDebug() << "========================================";
    qDebug() << "  已连接到聊天服务器";
    qDebug() << "  输入 /help 查看帮助";
    qDebug() << "========================================";
}

void ChatClient::onDisconnected()
{
    qDebug() << "\n已断开连接";
    QCoreApplication::quit();
}

void ChatClient::onReadyRead()
{
    while (m_socket->canReadLine()) {
        QByteArray line = m_socket->readLine();
        QString message = QString::fromUtf8(line).trimmed();
        if (!message.isEmpty()) {
            qDebug().noquote() << message;
        }
    }
}

void ChatClient::onError(QAbstractSocket::SocketError error)
{
    Q_UNUSED(error)
    qDebug() << "连接错误:" << m_socket->errorString();
    QCoreApplication::quit();
}

void ChatClient::onStdinReady()
{
    QString line = m_stdin.readLine();
    if (!line.isEmpty()) {
        if (line == "/quit") {
            m_socket->disconnectFromHost();
        } else {
            sendMessage(line);
        }
    }
}

void ChatClient::sendMessage(const QString &message)
{
    if (m_socket->state() == QAbstractSocket::ConnectedState) {
        m_socket->write((message + "\n").toUtf8());
        m_socket->flush();
    }
}

main.cpp(客户端)

cpp 复制代码
#include <QCoreApplication>
#include "chatclient.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    QString host = "127.0.0.1";
    quint16 port = 9999;
    
    if (argc > 1) host = argv[1];
    if (argc > 2) port = QString(argv[2]).toUShort();
    
    ChatClient client(host, port);
    client.connectToServer();
    
    return a.exec();
}
7.1.5 编译和运行
bash 复制代码
# 编译服务端
cd ChatServer
qmake && make  # 或 nmake (Windows)

# 编译客户端
cd ../ChatClient
qmake && make

# 运行
./ChatServer 9999          # 启动服务端
./ChatClient 127.0.0.1 9999  # 启动客户端

运行效果

复制代码
# 服务端输出
========================================
  聊天服务器已启动
  监听端口: 9999
========================================
[连接] 127.0.0.1 : 54321
[更名] user_54321 -> 张三
[消息] 张三 : 大家好!

# 客户端输出
========================================
  已连接到聊天服务器
  输入 /help 查看帮助
========================================
[系统] 欢迎加入聊天室!请发送 /name 你的昵称 设置昵称
/name 张三
[系统] user_54321 更名为 张三
大家好!
[14:30:25] 张三: 大家好!

7.2 项目二:HTTP文件下载器

7.2.1 项目概述

一个支持断点续传的HTTP文件下载器,提供命令行界面,显示下载进度和速度。

功能特点

  • 支持HTTP/HTTPS下载
  • 断点续传(暂停后可继续)
  • 实时显示下载进度和速度
  • 命令行交互控制
7.2.2 项目结构
复制代码
FileDownloader/
├── FileDownloader.pro
├── main.cpp
├── downloader.h
└── downloader.cpp
7.2.3 完整代码

FileDownloader.pro

qmake 复制代码
QT += core network
QT -= gui

CONFIG += c++17 console
CONFIG -= app_bundle

TARGET = FileDownloader
TEMPLATE = app

SOURCES += main.cpp downloader.cpp
HEADERS += downloader.h

downloader.h

cpp 复制代码
#ifndef DOWNLOADER_H
#define DOWNLOADER_H

#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QFile>
#include <QTimer>
#include <QElapsedTimer>
#include <QUrl>

class Downloader : public QObject
{
    Q_OBJECT
public:
    explicit Downloader(QObject *parent = nullptr);
    ~Downloader();
    
    void download(const QString &url, const QString &savePath);
    void pause();
    void resume();
    void cancel();
    
    bool isDownloading() const { return m_downloading; }
    bool isPaused() const { return m_paused; }

signals:
    void progressUpdated(qint64 bytesReceived, qint64 bytesTotal, 
                         double speed, int remainingSeconds);
    void finished(const QString &filePath);
    void error(const QString &errorString);
    void paused();
    void resumed();

private slots:
    void onReadyRead();
    void onFinished();
    void onError(QNetworkReply::NetworkError code);
    void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
    void updateSpeed();

private:
    void startDownload(qint64 startByte = 0);
    QString formatSize(qint64 bytes);
    QString formatTime(int seconds);
    
    QNetworkAccessManager *m_manager;
    QNetworkReply *m_reply;
    QFile *m_file;
    
    QString m_url;
    QString m_savePath;
    QString m_tempPath;
    
    qint64 m_downloadedBytes;
    qint64 m_totalBytes;
    qint64 m_lastBytes;
    
    QElapsedTimer m_speedTimer;
    QTimer *m_speedUpdateTimer;
    
    bool m_downloading;
    bool m_paused;
};

#endif // DOWNLOADER_H

downloader.cpp

cpp 复制代码
#include "downloader.h"
#include <QDebug>
#include <QFileInfo>
#include <QDir>

Downloader::Downloader(QObject *parent)
    : QObject(parent)
    , m_manager(new QNetworkAccessManager(this))
    , m_reply(nullptr)
    , m_file(nullptr)
    , m_downloadedBytes(0)
    , m_totalBytes(0)
    , m_lastBytes(0)
    , m_downloading(false)
    , m_paused(false)
{
    m_speedUpdateTimer = new QTimer(this);
    connect(m_speedUpdateTimer, &QTimer::timeout, this, &Downloader::updateSpeed);
}

Downloader::~Downloader()
{
    cancel();
}

void Downloader::download(const QString &url, const QString &savePath)
{
    if (m_downloading) {
        qDebug() << "已有下载任务在进行中";
        return;
    }
    
    m_url = url;
    m_savePath = savePath;
    m_tempPath = savePath + ".part";
    
    // 检查是否有未完成的下载
    QFileInfo tempFile(m_tempPath);
    qint64 startByte = 0;
    
    if (tempFile.exists()) {
        startByte = tempFile.size();
        m_downloadedBytes = startByte;
        qDebug() << "发现未完成下载,已下载:" << formatSize(startByte);
    } else {
        m_downloadedBytes = 0;
    }
    
    startDownload(startByte);
}

void Downloader::startDownload(qint64 startByte)
{
    // 打开文件(追加模式)
    m_file = new QFile(m_tempPath, this);
    QIODevice::OpenMode mode = startByte > 0 ? 
                               QIODevice::Append : QIODevice::WriteOnly;
    
    if (!m_file->open(mode)) {
        emit error("无法创建文件: " + m_file->errorString());
        return;
    }
    
    // 创建请求
    QNetworkRequest request(QUrl(m_url));
    request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
                         QNetworkRequest::NoLessSafeRedirectPolicy);
    
    // 设置断点续传
    if (startByte > 0) {
        QString range = QString("bytes=%1-").arg(startByte);
        request.setRawHeader("Range", range.toUtf8());
        qDebug() << "断点续传,从字节" << startByte << "开始";
    }
    
    // 发送请求
    m_reply = m_manager->get(request);
    
    connect(m_reply, &QNetworkReply::readyRead, 
            this, &Downloader::onReadyRead);
    connect(m_reply, &QNetworkReply::finished, 
            this, &Downloader::onFinished);
    connect(m_reply, &QNetworkReply::errorOccurred, 
            this, &Downloader::onError);
    connect(m_reply, &QNetworkReply::downloadProgress, 
            this, &Downloader::onDownloadProgress);
    
    m_downloading = true;
    m_paused = false;
    m_lastBytes = m_downloadedBytes;
    m_speedTimer.start();
    m_speedUpdateTimer->start(1000);  // 每秒更新速度
    
    qDebug() << "开始下载:" << m_url;
}

void Downloader::pause()
{
    if (!m_downloading || m_paused) return;
    
    m_paused = true;
    m_speedUpdateTimer->stop();
    
    if (m_reply) {
        m_reply->abort();
    }
    
    if (m_file) {
        m_file->close();
    }
    
    qDebug() << "下载已暂停,已下载:" << formatSize(m_downloadedBytes);
    emit paused();
}

void Downloader::resume()
{
    if (!m_paused) return;
    
    m_paused = false;
    qDebug() << "继续下载...";
    
    startDownload(m_downloadedBytes);
    emit resumed();
}

void Downloader::cancel()
{
    m_speedUpdateTimer->stop();
    
    if (m_reply) {
        m_reply->abort();
        m_reply->deleteLater();
        m_reply = nullptr;
    }
    
    if (m_file) {
        m_file->close();
        m_file->remove();  // 删除临时文件
        delete m_file;
        m_file = nullptr;
    }
    
    m_downloading = false;
    m_paused = false;
    m_downloadedBytes = 0;
    m_totalBytes = 0;
    
    qDebug() << "下载已取消";
}

void Downloader::onReadyRead()
{
    if (m_file && m_reply) {
        QByteArray data = m_reply->readAll();
        m_file->write(data);
        m_downloadedBytes += data.size();
    }
}

void Downloader::onFinished()
{
    m_speedUpdateTimer->stop();
    
    if (!m_reply) return;
    
    int statusCode = m_reply->attribute(
        QNetworkRequest::HttpStatusCodeAttribute).toInt();
    
    // 200: 完整下载, 206: 部分内容(断点续传)
    if (m_reply->error() == QNetworkReply::NoError || 
        statusCode == 200 || statusCode == 206) {
        
        if (m_file) {
            m_file->close();
        }
        
        // 重命名临时文件为最终文件
        QFile::remove(m_savePath);
        if (QFile::rename(m_tempPath, m_savePath)) {
            qDebug() << "\n✅ 下载完成:" << m_savePath;
            qDebug() << "   文件大小:" << formatSize(m_downloadedBytes);
            emit finished(m_savePath);
        } else {
            emit error("无法重命名文件");
        }
    }
    
    m_reply->deleteLater();
    m_reply = nullptr;
    m_downloading = false;
    
    if (m_file) {
        delete m_file;
        m_file = nullptr;
    }
}

void Downloader::onError(QNetworkReply::NetworkError code)
{
    if (code == QNetworkReply::OperationCanceledError && m_paused) {
        return;  // 暂停导致的取消,忽略
    }
    
    QString errorString = m_reply ? m_reply->errorString() : "未知错误";
    qDebug() << "下载错误:" << errorString;
    emit error(errorString);
}

void Downloader::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
    // bytesTotal是剩余大小(断点续传时),需要加上已下载部分
    if (m_totalBytes == 0 && bytesTotal > 0) {
        m_totalBytes = bytesTotal + (m_downloadedBytes - bytesReceived);
    }
}

void Downloader::updateSpeed()
{
    qint64 elapsed = m_speedTimer.elapsed();
    if (elapsed <= 0) return;
    
    qint64 bytesThisInterval = m_downloadedBytes - m_lastBytes;
    double speed = bytesThisInterval * 1000.0 / elapsed;  // bytes/sec
    
    m_lastBytes = m_downloadedBytes;
    m_speedTimer.restart();
    
    // 计算剩余时间
    int remaining = 0;
    if (speed > 0 && m_totalBytes > m_downloadedBytes) {
        remaining = (m_totalBytes - m_downloadedBytes) / speed;
    }
    
    emit progressUpdated(m_downloadedBytes, m_totalBytes, speed, remaining);
    
    // 打印进度
    double percent = m_totalBytes > 0 ? 
                     (m_downloadedBytes * 100.0 / m_totalBytes) : 0;
    
    QString progress = QString("\r📥 %1% | %2/%3 | %4/s | 剩余 %5")
        .arg(percent, 5, 'f', 1)
        .arg(formatSize(m_downloadedBytes))
        .arg(formatSize(m_totalBytes))
        .arg(formatSize(speed))
        .arg(formatTime(remaining));
    
    printf("%s", progress.toUtf8().constData());
    fflush(stdout);
}

QString Downloader::formatSize(qint64 bytes)
{
    if (bytes < 1024) {
        return QString("%1 B").arg(bytes);
    } else if (bytes < 1024 * 1024) {
        return QString("%1 KB").arg(bytes / 1024.0, 0, 'f', 1);
    } else if (bytes < 1024 * 1024 * 1024) {
        return QString("%1 MB").arg(bytes / 1024.0 / 1024.0, 0, 'f', 2);
    } else {
        return QString("%1 GB").arg(bytes / 1024.0 / 1024.0 / 1024.0, 0, 'f', 2);
    }
}

QString Downloader::formatTime(int seconds)
{
    if (seconds < 60) {
        return QString("%1秒").arg(seconds);
    } else if (seconds < 3600) {
        return QString("%1分%2秒").arg(seconds / 60).arg(seconds % 60);
    } else {
        return QString("%1时%2分").arg(seconds / 3600).arg((seconds % 3600) / 60);
    }
}

main.cpp

cpp 复制代码
#include <QCoreApplication>
#include <QTextStream>
#include <QSocketNotifier>
#include <QTimer>
#include "downloader.h"

class DownloadController : public QObject
{
    Q_OBJECT
public:
    DownloadController(const QString &url, const QString &savePath, QObject *parent = nullptr)
        : QObject(parent), m_url(url), m_savePath(savePath)
    {
        m_downloader = new Downloader(this);
        
        connect(m_downloader, &Downloader::finished, this, [](const QString &path) {
            qDebug() << "";
            QCoreApplication::quit();
        });
        
        connect(m_downloader, &Downloader::error, this, [](const QString &err) {
            qDebug() << "\n❌ 错误:" << err;
            QCoreApplication::exit(1);
        });
        
        // 监听标准输入
        m_stdin = new QTextStream(stdin);
        m_notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this);
        connect(m_notifier, &QSocketNotifier::activated, this, &DownloadController::onInput);
    }
    
    void start()
    {
        printHelp();
        m_downloader->download(m_url, m_savePath);
    }
    
private slots:
    void onInput()
    {
        QString cmd = m_stdin->readLine().trimmed().toLower();
        
        if (cmd == "p" || cmd == "pause") {
            m_downloader->pause();
        }
        else if (cmd == "r" || cmd == "resume") {
            m_downloader->resume();
        }
        else if (cmd == "c" || cmd == "cancel") {
            m_downloader->cancel();
            QCoreApplication::quit();
        }
        else if (cmd == "h" || cmd == "help") {
            printHelp();
        }
        else if (cmd == "q" || cmd == "quit") {
            m_downloader->cancel();
            QCoreApplication::quit();
        }
    }
    
private:
    void printHelp()
    {
        qDebug() << "========================================";
        qDebug() << "  HTTP文件下载器";
        qDebug() << "  命令: p(暂停) r(继续) c(取消) q(退出)";
        qDebug() << "========================================";
    }
    
    Downloader *m_downloader;
    QTextStream *m_stdin;
    QSocketNotifier *m_notifier;
    QString m_url;
    QString m_savePath;
};

#include "main.moc"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    if (argc < 2) {
        qDebug() << "用法: FileDownloader <URL> [保存路径]";
        qDebug() << "示例: FileDownloader https://example.com/file.zip";
        return 1;
    }
    
    QString url = argv[1];
    QString savePath;
    
    if (argc >= 3) {
        savePath = argv[2];
    } else {
        // 从URL提取文件名
        QUrl qurl(url);
        QString fileName = qurl.fileName();
        if (fileName.isEmpty()) {
            fileName = "download";
        }
        savePath = QDir::currentPath() + "/" + fileName;
    }
    
    qDebug() << "下载:" << url;
    qDebug() << "保存到:" << savePath;
    
    DownloadController controller(url, savePath);
    controller.start();
    
    return a.exec();
}
7.2.4 编译和运行
bash 复制代码
# 编译
qmake && make

# 运行
./FileDownloader https://releases.ubuntu.com/22.04/ubuntu-22.04-desktop-amd64.iso

# 运行效果
========================================
  HTTP文件下载器
  命令: p(暂停) r(继续) c(取消) q(退出)
========================================
下载: https://releases.ubuntu.com/22.04/ubuntu-22.04-desktop-amd64.iso
保存到: /home/user/ubuntu-22.04-desktop-amd64.iso
开始下载...
📥  15.3% | 589.2 MB/3.85 GB | 12.5 MB/s | 剩余 4分32秒

# 按 p 暂停
下载已暂停,已下载: 589.2 MB

# 按 r 继续
继续下载...
断点续传,从字节 617832448 开始
📥  16.1% | 620.8 MB/3.85 GB | 10.2 MB/s | 剩余 5分18秒

# 下载完成
✅ 下载完成: /home/user/ubuntu-22.04-desktop-amd64.iso
   文件大小: 3.85 GB

7.3 项目三:局域网设备发现

7.3.1 项目概述

一个基于UDP广播的局域网设备发现工具,包含设备端(响应发现请求)和发现端(搜索设备)。

功能特点

  • UDP广播发现局域网内设备
  • 设备信息展示(名称、IP、端口、类型)
  • 支持多设备同时在线
  • 自动刷新设备列表
7.3.2 项目结构
复制代码
LANDiscovery/
├── DeviceResponder/
│   ├── DeviceResponder.pro
│   ├── main.cpp
│   ├── deviceresponder.h
│   └── deviceresponder.cpp
└── DeviceDiscoverer/
    ├── DeviceDiscoverer.pro
    ├── main.cpp
    ├── devicediscoverer.h
    └── devicediscoverer.cpp
7.3.3 设备响应端完整代码

DeviceResponder.pro

qmake 复制代码
QT += core network
QT -= gui

CONFIG += c++17 console
CONFIG -= app_bundle

TARGET = DeviceResponder
TEMPLATE = app

SOURCES += main.cpp deviceresponder.cpp
HEADERS += deviceresponder.h

deviceresponder.h

cpp 复制代码
#ifndef DEVICERESPONDER_H
#define DEVICERESPONDER_H

#include <QObject>
#include <QUdpSocket>
#include <QJsonObject>
#include <QHostInfo>
#include <QNetworkInterface>

class DeviceResponder : public QObject
{
    Q_OBJECT
public:
    explicit DeviceResponder(quint16 port, QObject *parent = nullptr);
    
    void setDeviceInfo(const QString &name, const QString &type, 
                       quint16 servicePort);
    bool start();
    void stop();

private slots:
    void onReadyRead();

private:
    QJsonObject createResponse();
    QString getLocalIP();
    
    QUdpSocket *m_socket;
    quint16 m_port;
    
    QString m_deviceName;
    QString m_deviceType;
    quint16 m_servicePort;
    
    static const QString DISCOVERY_MAGIC;
};

#endif // DEVICERESPONDER_H

deviceresponder.cpp

cpp 复制代码
#include "deviceresponder.h"
#include <QDebug>
#include <QJsonDocument>
#include <QDateTime>
#include <QCoreApplication>

const QString DeviceResponder::DISCOVERY_MAGIC = "DEVICE_DISCOVERY_V1";

DeviceResponder::DeviceResponder(quint16 port, QObject *parent)
    : QObject(parent)
    , m_socket(new QUdpSocket(this))
    , m_port(port)
    , m_deviceName(QHostInfo::localHostName())
    , m_deviceType("generic")
    , m_servicePort(0)
{
    connect(m_socket, &QUdpSocket::readyRead, 
            this, &DeviceResponder::onReadyRead);
}

void DeviceResponder::setDeviceInfo(const QString &name, const QString &type, 
                                     quint16 servicePort)
{
    m_deviceName = name;
    m_deviceType = type;
    m_servicePort = servicePort;
}

bool DeviceResponder::start()
{
    if (m_socket->bind(m_port, QUdpSocket::ShareAddress | 
                                QUdpSocket::ReuseAddressHint)) {
        qDebug() << "========================================";
        qDebug() << "  设备响应服务已启动";
        qDebug() << "  设备名称:" << m_deviceName;
        qDebug() << "  设备类型:" << m_deviceType;
        qDebug() << "  服务端口:" << m_servicePort;
        qDebug() << "  监听端口:" << m_port;
        qDebug() << "  本机IP:" << getLocalIP();
        qDebug() << "========================================";
        return true;
    }
    
    qDebug() << "启动失败:" << m_socket->errorString();
    return false;
}

void DeviceResponder::stop()
{
    m_socket->close();
    qDebug() << "设备响应服务已停止";
}

void DeviceResponder::onReadyRead()
{
    while (m_socket->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(m_socket->pendingDatagramSize());
        QHostAddress senderAddress;
        quint16 senderPort;
        
        m_socket->readDatagram(datagram.data(), datagram.size(),
                               &senderAddress, &senderPort);
        
        QString message = QString::fromUtf8(datagram);
        
        // 检查是否是发现请求
        if (message.startsWith(DISCOVERY_MAGIC)) {
            qDebug() << "收到发现请求 from" 
                     << senderAddress.toString() << ":" << senderPort;
            
            // 发送响应
            QJsonObject response = createResponse();
            QByteArray responseData = QJsonDocument(response).toJson(
                QJsonDocument::Compact);
            
            m_socket->writeDatagram(responseData, senderAddress, senderPort);
            qDebug() << "已发送响应";
        }
    }
}

QJsonObject DeviceResponder::createResponse()
{
    QJsonObject response;
    response["magic"] = DISCOVERY_MAGIC;
    response["name"] = m_deviceName;
    response["type"] = m_deviceType;
    response["ip"] = getLocalIP();
    response["port"] = m_servicePort;
    response["timestamp"] = QDateTime::currentMSecsSinceEpoch();
    response["platform"] = QSysInfo::prettyProductName();
    
    return response;
}

QString DeviceResponder::getLocalIP()
{
    const auto interfaces = QNetworkInterface::allInterfaces();
    for (const QNetworkInterface &interface : interfaces) {
        if (interface.flags().testFlag(QNetworkInterface::IsUp) &&
            interface.flags().testFlag(QNetworkInterface::IsRunning) &&
            !interface.flags().testFlag(QNetworkInterface::IsLoopBack)) {
            
            const auto entries = interface.addressEntries();
            for (const QNetworkAddressEntry &entry : entries) {
                if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
                    return entry.ip().toString();
                }
            }
        }
    }
    return "127.0.0.1";
}

main.cpp(设备响应端)

cpp 复制代码
#include <QCoreApplication>
#include "deviceresponder.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    quint16 discoveryPort = 45454;
    QString deviceName = "MyDevice";
    QString deviceType = "computer";
    quint16 servicePort = 8080;
    
    // 解析命令行参数
    for (int i = 1; i < argc; ++i) {
        QString arg = argv[i];
        if (arg == "-n" && i + 1 < argc) {
            deviceName = argv[++i];
        } else if (arg == "-t" && i + 1 < argc) {
            deviceType = argv[++i];
        } else if (arg == "-p" && i + 1 < argc) {
            servicePort = QString(argv[++i]).toUShort();
        } else if (arg == "-d" && i + 1 < argc) {
            discoveryPort = QString(argv[++i]).toUShort();
        } else if (arg == "-h" || arg == "--help") {
            qDebug() << "用法: DeviceResponder [选项]";
            qDebug() << "  -n <名称>    设备名称 (默认: MyDevice)";
            qDebug() << "  -t <类型>    设备类型 (默认: computer)";
            qDebug() << "  -p <端口>    服务端口 (默认: 8080)";
            qDebug() << "  -d <端口>    发现端口 (默认: 45454)";
            return 0;
        }
    }
    
    DeviceResponder responder(discoveryPort);
    responder.setDeviceInfo(deviceName, deviceType, servicePort);
    
    if (!responder.start()) {
        return 1;
    }
    
    return a.exec();
}
7.3.4 设备发现端完整代码

DeviceDiscoverer.pro

qmake 复制代码
QT += core network
QT -= gui

CONFIG += c++17 console
CONFIG -= app_bundle

TARGET = DeviceDiscoverer
TEMPLATE = app

SOURCES += main.cpp devicediscoverer.cpp
HEADERS += devicediscoverer.h

devicediscoverer.h

cpp 复制代码
#ifndef DEVICEDISCOVERER_H
#define DEVICEDISCOVERER_H

#include <QObject>
#include <QUdpSocket>
#include <QTimer>
#include <QJsonObject>
#include <QMap>

struct DeviceInfo {
    QString name;
    QString type;
    QString ip;
    quint16 port;
    QString platform;
    qint64 lastSeen;
};

class DeviceDiscoverer : public QObject
{
    Q_OBJECT
public:
    explicit DeviceDiscoverer(quint16 port, QObject *parent = nullptr);
    
    void startDiscovery(int intervalMs = 3000);
    void stopDiscovery();
    void discover();
    
    QList<DeviceInfo> getDevices() const;

signals:
    void deviceFound(const DeviceInfo &device);
    void deviceLost(const QString &ip);
    void discoveryCompleted(int count);

private slots:
    void onReadyRead();
    void sendDiscoveryRequest();
    void cleanupOldDevices();

private:
    void printDeviceTable();
    
    QUdpSocket *m_socket;
    QTimer *m_discoveryTimer;
    QTimer *m_cleanupTimer;
    quint16 m_port;
    
    QMap<QString, DeviceInfo> m_devices;  // IP -> DeviceInfo
    
    static const QString DISCOVERY_MAGIC;
    static const int DEVICE_TIMEOUT_MS = 15000;  // 15秒无响应则移除
};

#endif // DEVICEDISCOVERER_H

devicediscoverer.cpp

cpp 复制代码
#include "devicediscoverer.h"
#include <QDebug>
#include <QJsonDocument>
#include <QDateTime>
#include <QHostAddress>
#include <QNetworkInterface>

const QString DeviceDiscoverer::DISCOVERY_MAGIC = "DEVICE_DISCOVERY_V1";

DeviceDiscoverer::DeviceDiscoverer(quint16 port, QObject *parent)
    : QObject(parent)
    , m_socket(new QUdpSocket(this))
    , m_port(port)
{
    connect(m_socket, &QUdpSocket::readyRead, 
            this, &DeviceDiscoverer::onReadyRead);
    
    m_discoveryTimer = new QTimer(this);
    connect(m_discoveryTimer, &QTimer::timeout, 
            this, &DeviceDiscoverer::sendDiscoveryRequest);
    
    m_cleanupTimer = new QTimer(this);
    connect(m_cleanupTimer, &QTimer::timeout, 
            this, &DeviceDiscoverer::cleanupOldDevices);
    
    // 绑定随机端口接收响应
    m_socket->bind(QHostAddress::Any, 0);
}

void DeviceDiscoverer::startDiscovery(int intervalMs)
{
    qDebug() << "========================================";
    qDebug() << "  局域网设备发现工具";
    qDebug() << "  目标端口:" << m_port;
    qDebug() << "  刷新间隔:" << intervalMs / 1000 << "秒";
    qDebug() << "========================================\n";
    
    m_discoveryTimer->start(intervalMs);
    m_cleanupTimer->start(5000);  // 每5秒清理一次
    
    // 立即发送一次
    sendDiscoveryRequest();
}

void DeviceDiscoverer::stopDiscovery()
{
    m_discoveryTimer->stop();
    m_cleanupTimer->stop();
    qDebug() << "发现服务已停止";
}

void DeviceDiscoverer::discover()
{
    sendDiscoveryRequest();
}

QList<DeviceInfo> DeviceDiscoverer::getDevices() const
{
    return m_devices.values();
}

void DeviceDiscoverer::sendDiscoveryRequest()
{
    QByteArray request = (DISCOVERY_MAGIC + ":DISCOVER").toUtf8();
    
    // 发送广播
    m_socket->writeDatagram(request, QHostAddress::Broadcast, m_port);
    
    qDebug() << "📡 发送发现广播...";
}

void DeviceDiscoverer::onReadyRead()
{
    while (m_socket->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(m_socket->pendingDatagramSize());
        QHostAddress senderAddress;
        quint16 senderPort;
        
        m_socket->readDatagram(datagram.data(), datagram.size(),
                               &senderAddress, &senderPort);
        
        // 解析JSON响应
        QJsonDocument doc = QJsonDocument::fromJson(datagram);
        if (!doc.isObject()) continue;
        
        QJsonObject obj = doc.object();
        
        // 验证魔数
        if (obj["magic"].toString() != DISCOVERY_MAGIC) continue;
        
        QString ip = obj["ip"].toString();
        if (ip.isEmpty()) ip = senderAddress.toString();
        
        DeviceInfo device;
        device.name = obj["name"].toString();
        device.type = obj["type"].toString();
        device.ip = ip;
        device.port = obj["port"].toInt();
        device.platform = obj["platform"].toString();
        device.lastSeen = QDateTime::currentMSecsSinceEpoch();
        
        bool isNew = !m_devices.contains(ip);
        m_devices[ip] = device;
        
        if (isNew) {
            qDebug() << "✅ 发现新设备:" << device.name 
                     << "(" << device.ip << ")";
            emit deviceFound(device);
        }
    }
    
    // 打印设备表格
    printDeviceTable();
    emit discoveryCompleted(m_devices.size());
}

void DeviceDiscoverer::cleanupOldDevices()
{
    qint64 now = QDateTime::currentMSecsSinceEpoch();
    QStringList toRemove;
    
    for (auto it = m_devices.begin(); it != m_devices.end(); ++it) {
        if (now - it.value().lastSeen > DEVICE_TIMEOUT_MS) {
            toRemove.append(it.key());
        }
    }
    
    for (const QString &ip : toRemove) {
        QString name = m_devices[ip].name;
        m_devices.remove(ip);
        qDebug() << "❌ 设备离线:" << name << "(" << ip << ")";
        emit deviceLost(ip);
    }
}

void DeviceDiscoverer::printDeviceTable()
{
    if (m_devices.isEmpty()) {
        qDebug() << "未发现任何设备\n";
        return;
    }
    
    qDebug() << "\n┌─────────────────────────────────────────────────────────────────┐";
    qDebug() << "│ 设备名称          │ IP地址          │ 端口  │ 类型       │";
    qDebug() << "├─────────────────────────────────────────────────────────────────┤";
    
    for (const DeviceInfo &device : m_devices) {
        QString line = QString("│ %-17s │ %-15s │ %-5d │ %-10s │")
            .arg(device.name.left(17))
            .arg(device.ip)
            .arg(device.port)
            .arg(device.type.left(10));
        qDebug().noquote() << line;
    }
    
    qDebug() << "└─────────────────────────────────────────────────────────────────┘";
    qDebug() << "在线设备:" << m_devices.size() << "台\n";
}

main.cpp(设备发现端)

cpp 复制代码
#include <QCoreApplication>
#include <QTextStream>
#include <QSocketNotifier>
#include "devicediscoverer.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    quint16 discoveryPort = 45454;
    int interval = 5000;  // 5秒
    
    // 解析命令行参数
    for (int i = 1; i < argc; ++i) {
        QString arg = argv[i];
        if (arg == "-p" && i + 1 < argc) {
            discoveryPort = QString(argv[++i]).toUShort();
        } else if (arg == "-i" && i + 1 < argc) {
            interval = QString(argv[++i]).toInt() * 1000;
        } else if (arg == "-h" || arg == "--help") {
            qDebug() << "用法: DeviceDiscoverer [选项]";
            qDebug() << "  -p <端口>    发现端口 (默认: 45454)";
            qDebug() << "  -i <秒>      刷新间隔 (默认: 5)";
            return 0;
        }
    }
    
    DeviceDiscoverer discoverer(discoveryPort);
    
    // 监听键盘输入
    QTextStream stdin(::stdin);
    QSocketNotifier notifier(fileno(::stdin), QSocketNotifier::Read);
    
    QObject::connect(&notifier, &QSocketNotifier::activated, [&]() {
        QString cmd = stdin.readLine().trimmed().toLower();
        if (cmd == "r" || cmd == "refresh") {
            discoverer.discover();
        } else if (cmd == "l" || cmd == "list") {
            auto devices = discoverer.getDevices();
            qDebug() << "\n当前设备列表:";
            for (const auto &d : devices) {
                qDebug() << "  -" << d.name << "(" << d.ip << ":" << d.port << ")";
            }
        } else if (cmd == "q" || cmd == "quit") {
            QCoreApplication::quit();
        } else if (cmd == "h" || cmd == "help") {
            qDebug() << "命令: r(立即刷新) l(列表) q(退出) h(帮助)";
        }
    });
    
    qDebug() << "命令: r(立即刷新) l(列表) q(退出) h(帮助)\n";
    
    discoverer.startDiscovery(interval);
    
    return a.exec();
}
7.3.5 编译和运行
bash 复制代码
# 编译设备响应端
cd DeviceResponder
qmake && make

# 编译设备发现端
cd ../DeviceDiscoverer
qmake && make

# 运行设备响应端(在不同电脑或终端运行多个实例)
./DeviceResponder -n "客厅电脑" -t computer -p 8080
./DeviceResponder -n "办公室打印机" -t printer -p 9100
./DeviceResponder -n "智能电视" -t tv -p 8443

# 运行设备发现端
./DeviceDiscoverer

# 运行效果
========================================
  局域网设备发现工具
  目标端口: 45454
  刷新间隔: 5秒
========================================

命令: r(立即刷新) l(列表) q(退出) h(帮助)

📡 发送发现广播...
✅ 发现新设备: 客厅电脑 (192.168.1.100)
✅ 发现新设备: 办公室打印机 (192.168.1.101)
✅ 发现新设备: 智能电视 (192.168.1.102)

┌─────────────────────────────────────────────────────────────────┐
│ 设备名称          │ IP地址          │ 端口  │ 类型       │
├─────────────────────────────────────────────────────────────────┤
│ 客厅电脑          │ 192.168.1.100   │ 8080  │ computer   │
│ 办公室打印机       │ 192.168.1.101   │ 9100  │ printer    │
│ 智能电视          │ 192.168.1.102   │ 8443  │ tv         │
└─────────────────────────────────────────────────────────────────┘
在线设备: 3台

第七章小结

本章通过三个完整的实战项目,涵盖了Qt网络编程的主要应用场景:

项目 技术栈 应用场景
TCP聊天程序 QTcpServer + QTcpSocket 即时通讯、游戏服务器
HTTP文件下载器 QNetworkAccessManager 文件传输、更新下载
局域网设备发现 QUdpSocket + 广播 智能家居、局域网工具

每个项目都可以作为实际应用的基础进行扩展:

  • 聊天程序可加入私聊、文件传输、历史记录
  • 下载器可加入多线程分段下载、下载队列
  • 设备发现可加入设备控制、服务调用

📋 第八章:进阶主题

本章介绍Qt网络编程的进阶技术,帮助你构建更专业的网络应用。


8.1 异步DNS解析

8.1.1 QHostInfo异步查询
cpp 复制代码
#include <QHostInfo>

class DnsResolver : public QObject
{
    Q_OBJECT
public:
    void resolve(const QString &hostname) {
        // 异步DNS查询
        int lookupId = QHostInfo::lookupHost(hostname, 
            this, SLOT(onLookupFinished(QHostInfo)));
        
        qDebug() << "开始解析:" << hostname << "ID:" << lookupId;
    }
    
    // 取消查询
    void cancel(int lookupId) {
        QHostInfo::abortHostLookup(lookupId);
    }

private slots:
    void onLookupFinished(const QHostInfo &host) {
        if (host.error() != QHostInfo::NoError) {
            qDebug() << "DNS解析失败:" << host.errorString();
            return;
        }
        
        qDebug() << "域名:" << host.hostName();
        
        const auto addresses = host.addresses();
        for (const QHostAddress &addr : addresses) {
            if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
                qDebug() << "  IPv4:" << addr.toString();
            } else if (addr.protocol() == QAbstractSocket::IPv6Protocol) {
                qDebug() << "  IPv6:" << addr.toString();
            }
        }
    }
};

// 同步查询(会阻塞)
void syncLookup(const QString &hostname) {
    QHostInfo info = QHostInfo::fromName(hostname);
    // 注意:这会阻塞当前线程!
}
8.1.2 DNS缓存管理
cpp 复制代码
class DnsCache {
public:
    static DnsCache& instance() {
        static DnsCache cache;
        return cache;
    }
    
    QHostAddress lookup(const QString &hostname) {
        QMutexLocker locker(&m_mutex);
        
        auto it = m_cache.find(hostname);
        if (it != m_cache.end()) {
            // 检查是否过期(5分钟TTL)
            if (it->timestamp.secsTo(QDateTime::currentDateTime()) < 300) {
                return it->address;
            }
        }
        return QHostAddress();
    }
    
    void store(const QString &hostname, const QHostAddress &address) {
        QMutexLocker locker(&m_mutex);
        m_cache[hostname] = {address, QDateTime::currentDateTime()};
    }
    
    void clear() {
        QMutexLocker locker(&m_mutex);
        m_cache.clear();
    }
    
private:
    struct CacheEntry {
        QHostAddress address;
        QDateTime timestamp;
    };
    
    QMap<QString, CacheEntry> m_cache;
    QMutex m_mutex;
};

8.2 代理服务器配置

8.2.1 设置HTTP/SOCKS代理
cpp 复制代码
#include <QNetworkProxy>

class ProxyManager {
public:
    // 设置HTTP代理
    static void setHttpProxy(const QString &host, quint16 port,
                             const QString &user = QString(),
                             const QString &password = QString()) {
        QNetworkProxy proxy;
        proxy.setType(QNetworkProxy::HttpProxy);
        proxy.setHostName(host);
        proxy.setPort(port);
        
        if (!user.isEmpty()) {
            proxy.setUser(user);
            proxy.setPassword(password);
        }
        
        QNetworkProxy::setApplicationProxy(proxy);
        qDebug() << "已设置HTTP代理:" << host << ":" << port;
    }
    
    // 设置SOCKS5代理
    static void setSocks5Proxy(const QString &host, quint16 port,
                               const QString &user = QString(),
                               const QString &password = QString()) {
        QNetworkProxy proxy;
        proxy.setType(QNetworkProxy::Socks5Proxy);
        proxy.setHostName(host);
        proxy.setPort(port);
        
        if (!user.isEmpty()) {
            proxy.setUser(user);
            proxy.setPassword(password);
        }
        
        QNetworkProxy::setApplicationProxy(proxy);
        qDebug() << "已设置SOCKS5代理:" << host << ":" << port;
    }
    
    // 使用系统代理
    static void useSystemProxy() {
        QNetworkProxyFactory::setUseSystemConfiguration(true);
        qDebug() << "使用系统代理设置";
    }
    
    // 禁用代理
    static void noProxy() {
        QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
        qDebug() << "已禁用代理";
    }
    
    // 为特定请求设置代理
    static void setProxyForRequest(QNetworkRequest &request, 
                                   const QNetworkProxy &proxy) {
        // Qt 5.9+
        request.setAttribute(QNetworkRequest::ProxyAttribute, 
                             QVariant::fromValue(proxy));
    }
};

// 使用示例
void configureProxy() {
    // 方式1:全局代理
    ProxyManager::setHttpProxy("127.0.0.1", 8080);
    
    // 方式2:单个请求代理
    QNetworkRequest request(QUrl("https://api.example.com"));
    QNetworkProxy proxy(QNetworkProxy::HttpProxy, "proxy.local", 3128);
    request.setAttribute(QNetworkRequest::ProxyAttribute, 
                         QVariant::fromValue(proxy));
}
8.2.2 代理自动配置(PAC)
cpp 复制代码
class PacProxyFactory : public QNetworkProxyFactory
{
public:
    PacProxyFactory(const QString &pacUrl) : m_pacUrl(pacUrl) {
        downloadPac();
    }
    
    QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query) override {
        QString host = query.peerHostName();
        
        // 简化的PAC解析逻辑
        if (shouldBypass(host)) {
            return {QNetworkProxy::NoProxy};
        }
        
        return {m_proxy};
    }
    
private:
    void downloadPac() {
        // 下载并解析PAC文件
        // 实际实现需要JavaScript引擎解析PAC脚本
    }
    
    bool shouldBypass(const QString &host) {
        // 检查是否应该绑过代理
        return host == "localhost" || 
               host.endsWith(".local") ||
               host.startsWith("192.168.");
    }
    
    QString m_pacUrl;
    QNetworkProxy m_proxy;
};

// 安装自定义代理工厂
void installPacProxy() {
    QNetworkProxyFactory::setApplicationProxyFactory(
        new PacProxyFactory("http://proxy.example.com/proxy.pac")
    );
}

8.3 网络状态监测

8.3.1 使用QNetworkInformation (Qt 6.1+)
cpp 复制代码
#include <QNetworkInformation>

class NetworkMonitor : public QObject
{
    Q_OBJECT
public:
    NetworkMonitor(QObject *parent = nullptr) : QObject(parent) {
        // 加载网络信息后端
        if (!QNetworkInformation::loadDefaultBackend()) {
            qWarning() << "无法加载网络信息后端";
            return;
        }
        
        auto *netInfo = QNetworkInformation::instance();
        if (!netInfo) return;
        
        // 监听可达性变化
        connect(netInfo, &QNetworkInformation::reachabilityChanged,
                this, &NetworkMonitor::onReachabilityChanged);
        
        // 监听传输介质变化
        connect(netInfo, &QNetworkInformation::transportMediumChanged,
                this, &NetworkMonitor::onTransportChanged);
        
        // 初始状态
        printStatus();
    }
    
    bool isOnline() const {
        auto *netInfo = QNetworkInformation::instance();
        if (!netInfo) return true;  // 假设在线
        
        return netInfo->reachability() == 
               QNetworkInformation::Reachability::Online;
    }

private slots:
    void onReachabilityChanged(QNetworkInformation::Reachability reachability) {
        QString status;
        switch (reachability) {
        case QNetworkInformation::Reachability::Unknown:
            status = "未知";
            break;
        case QNetworkInformation::Reachability::Disconnected:
            status = "断开连接";
            emit networkLost();
            break;
        case QNetworkInformation::Reachability::Local:
            status = "仅本地网络";
            break;
        case QNetworkInformation::Reachability::Site:
            status = "仅局域网";
            break;
        case QNetworkInformation::Reachability::Online:
            status = "在线";
            emit networkRestored();
            break;
        }
        qDebug() << "🌐 网络状态:" << status;
    }
    
    void onTransportChanged(QNetworkInformation::TransportMedium medium) {
        QString type;
        switch (medium) {
        case QNetworkInformation::TransportMedium::Ethernet:
            type = "有线网络";
            break;
        case QNetworkInformation::TransportMedium::WiFi:
            type = "WiFi";
            break;
        case QNetworkInformation::TransportMedium::Cellular:
            type = "蜂窝网络";
            break;
        default:
            type = "其他";
        }
        qDebug() << "📡 传输介质:" << type;
    }

signals:
    void networkLost();
    void networkRestored();
    
private:
    void printStatus() {
        auto *netInfo = QNetworkInformation::instance();
        if (!netInfo) return;
        
        qDebug() << "========================================";
        qDebug() << "  网络状态监测已启动";
        qDebug() << "  后端:" << netInfo->backendName();
        onReachabilityChanged(netInfo->reachability());
        onTransportChanged(netInfo->transportMedium());
        qDebug() << "========================================";
    }
};
8.3.2 网络连通性检测
cpp 复制代码
class ConnectivityChecker : public QObject
{
    Q_OBJECT
public:
    ConnectivityChecker(QObject *parent = nullptr) : QObject(parent) {
        m_manager = new QNetworkAccessManager(this);
        
        m_timer = new QTimer(this);
        connect(m_timer, &QTimer::timeout, this, &ConnectivityChecker::check);
    }
    
    void startMonitoring(int intervalMs = 30000) {
        m_timer->start(intervalMs);
        check();  // 立即检测一次
    }
    
    void check() {
        // 检测多个可靠的服务
        QStringList urls = {
            "https://www.google.com/generate_204",
            "https://www.apple.com/library/test/success.html",
            "https://connectivity-check.ubuntu.com"
        };
        
        m_pendingChecks = urls.size();
        m_successCount = 0;
        
        for (const QString &url : urls) {
            QNetworkRequest request(QUrl(url));
            request.setTransferTimeout(5000);
            
            QNetworkReply *reply = m_manager->head(request);
            connect(reply, &QNetworkReply::finished, this, [this, reply]() {
                if (reply->error() == QNetworkReply::NoError) {
                    m_successCount++;
                }
                reply->deleteLater();
                
                m_pendingChecks--;
                if (m_pendingChecks == 0) {
                    bool online = m_successCount > 0;
                    if (online != m_lastOnline) {
                        m_lastOnline = online;
                        emit connectivityChanged(online);
                        qDebug() << "网络连通性:" << (online ? "在线" : "离线");
                    }
                }
            });
        }
    }

signals:
    void connectivityChanged(bool online);
    
private:
    QNetworkAccessManager *m_manager;
    QTimer *m_timer;
    int m_pendingChecks = 0;
    int m_successCount = 0;
    bool m_lastOnline = true;
};

8.4 Qt 6网络新特性

8.4.1 QNetworkInformation

Qt 6.1引入了QNetworkInformation类,提供系统级网络状态信息:

cpp 复制代码
// 支持的功能查询
auto features = QNetworkInformation::supportedFeatures();
if (features.testFlag(QNetworkInformation::Feature::Reachability)) {
    qDebug() << "支持可达性检测";
}
if (features.testFlag(QNetworkInformation::Feature::CaptivePortal)) {
    qDebug() << "支持强制门户检测";
}
if (features.testFlag(QNetworkInformation::Feature::TransportMedium)) {
    qDebug() << "支持传输介质检测";
}
if (features.testFlag(QNetworkInformation::Feature::Metered)) {
    qDebug() << "支持计量网络检测";
}
8.4.2 HTTP/2支持

Qt 6默认启用HTTP/2:

cpp 复制代码
// 检查是否使用了HTTP/2
connect(reply, &QNetworkReply::finished, [reply]() {
    // 获取HTTP版本
    QVariant httpVersion = reply->attribute(
        QNetworkRequest::Http2WasUsedAttribute);
    
    if (httpVersion.toBool()) {
        qDebug() << "使用HTTP/2协议";
    } else {
        qDebug() << "使用HTTP/1.1协议";
    }
});

// 强制使用HTTP/1.1(如果需要)
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
8.4.3 改进的SSL/TLS
cpp 复制代码
// Qt 6中的SSL配置
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();

// 设置最低TLS版本
sslConfig.setProtocol(QSsl::TlsV1_2OrLater);

// ALPN协议协商
sslConfig.setAllowedNextProtocols({
    QSslConfiguration::NextProtocolHttp1_1,
    QSslConfiguration::ALPNProtocolHTTP2
});

// 设置会话票证(Session Tickets)
sslConfig.setSessionTickets({});  // 使用默认

request.setSslConfiguration(sslConfig);

8.5 性能调优

8.5.1 连接池管理
cpp 复制代码
class ConnectionPool {
public:
    static QNetworkAccessManager* getManager() {
        // 线程局部单例
        thread_local QNetworkAccessManager manager;
        
        // 配置连接限制
        // 注意:这些是Qt内部设置,可能需要通过环境变量配置
        // 例如:QT_HTTP_MAX_CONNECTIONS_PER_HOST
        
        return &manager;
    }
    
    static void configure() {
        // 通过环境变量配置
        qputenv("QT_HTTP_MAX_CONNECTIONS_PER_HOST", "6");
    }
};
8.5.2 请求优化
cpp 复制代码
class OptimizedRequest {
public:
    static QNetworkRequest create(const QUrl &url) {
        QNetworkRequest request(url);
        
        // 启用HTTP/2
        request.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
        
        // 启用管道(HTTP/1.1)
        request.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
        
        // 设置超时
        request.setTransferTimeout(30000);
        
        // 启用压缩
        request.setRawHeader("Accept-Encoding", "gzip, deflate");
        
        // 缓存策略
        request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
                             QNetworkRequest::PreferCache);
        
        return request;
    }
};
8.5.3 大文件传输优化
cpp 复制代码
class LargeFileTransfer : public QObject
{
    Q_OBJECT
public:
    void uploadLargeFile(const QString &url, const QString &filePath) {
        QFile *file = new QFile(filePath, this);
        if (!file->open(QIODevice::ReadOnly)) {
            emit error("无法打开文件");
            return;
        }
        
        QNetworkRequest request(QUrl(url));
        request.setHeader(QNetworkRequest::ContentLengthHeader, file->size());
        
        // 分块传输
        request.setRawHeader("Transfer-Encoding", "chunked");
        
        QNetworkReply *reply = m_manager->post(request, file);
        file->setParent(reply);  // reply完成后自动删除file
        
        connect(reply, &QNetworkReply::uploadProgress,
                this, [this](qint64 sent, qint64 total) {
            double percent = total > 0 ? (sent * 100.0 / total) : 0;
            qDebug() << QString("上传进度: %1%").arg(percent, 0, 'f', 1);
        });
    }
    
signals:
    void error(const QString &msg);
    
private:
    QNetworkAccessManager *m_manager = new QNetworkAccessManager(this);
};

第八章小结

本章介绍了Qt网络编程的进阶主题:

主题 关键类/技术
异步DNS QHostInfo::lookupHost()
代理配置 QNetworkProxy, QNetworkProxyFactory
网络监测 QNetworkInformation (Qt 6.1+)
Qt 6新特性 HTTP/2, 改进的SSL/TLS
性能调优 连接池、请求优化、分块传输

附录

A. Qt网络模块API速查表

A.1 核心类
类名 用途 常用方法
QTcpServer TCP服务器 listen(), nextPendingConnection()
QTcpSocket TCP客户端 connectToHost(), write(), read()
QUdpSocket UDP通信 bind(), writeDatagram(), readDatagram()
QNetworkAccessManager HTTP客户端 get(), post(), put(), delete()
QNetworkRequest HTTP请求 setUrl(), setHeader(), setRawHeader()
QNetworkReply HTTP响应 readAll(), error(), attribute()
QWebSocket WebSocket客户端 open(), sendTextMessage(), close()
QWebSocketServer WebSocket服务器 listen(), nextPendingConnection()
A.2 常用信号
cpp 复制代码
// QTcpSocket / QUdpSocket
void connected();
void disconnected();
void readyRead();
void errorOccurred(QAbstractSocket::SocketError);
void stateChanged(QAbstractSocket::SocketState);

// QNetworkReply
void finished();
void downloadProgress(qint64, qint64);
void uploadProgress(qint64, qint64);
void errorOccurred(QNetworkReply::NetworkError);
void sslErrors(const QList<QSslError>&);

// QWebSocket
void connected();
void disconnected();
void textMessageReceived(const QString&);
void binaryMessageReceived(const QByteArray&);
void pong(quint64, const QByteArray&);
A.3 .pro文件配置
qmake 复制代码
# TCP/UDP
QT += network

# WebSocket
QT += websockets

# SSL支持(通常自动包含)
# 需确保系统安装OpenSSL

B. 常见网络错误码与解决方案

B.1 QAbstractSocket::SocketError
错误码 名称 原因 解决方案
0 ConnectionRefusedError 目标拒绝连接 检查服务是否运行、端口是否正确
1 RemoteHostClosedError 远程主机关闭 正常断开或实现重连机制
2 HostNotFoundError 找不到主机 检查域名/IP是否正确
3 SocketAccessError 权限不足 Linux下绑定<1024端口需root
4 SocketResourceError 资源不足 检查文件描述符限制
5 SocketTimeoutError 连接超时 检查网络、增加超时时间
B.2 QNetworkReply::NetworkError
错误码 名称 HTTP状态 解决方案
1 ConnectionRefusedError - 检查服务器是否在线
2 RemoteHostClosedError - 实现重连机制
3 HostNotFoundError - 检查DNS、网络连接
4 TimeoutError - 增加超时时间、检查网络
99 UnknownNetworkError - 查看errorString()
201 ContentAccessDenied 403 检查权限、认证Token
203 ContentNotFoundError 404 检查URL是否正确
204 AuthenticationRequiredError 401 提供有效认证信息
302 InsecureRedirectError 3xx 设置重定向策略

C. HTTP状态码速查

C.1 成功 (2xx)
状态码 名称 说明
200 OK 请求成功
201 Created 资源创建成功
204 No Content 成功但无返回内容
206 Partial Content 断点续传返回部分内容
C.2 重定向 (3xx)
状态码 名称 说明
301 Moved Permanently 永久重定向
302 Found 临时重定向
304 Not Modified 资源未修改,使用缓存
C.3 客户端错误 (4xx)
状态码 名称 说明
400 Bad Request 请求参数错误
401 Unauthorized 未认证
403 Forbidden 无权限
404 Not Found 资源不存在
405 Method Not Allowed HTTP方法不允许
429 Too Many Requests 请求频率限制
C.4 服务端错误 (5xx)
状态码 名称 说明
500 Internal Server Error 服务器内部错误
502 Bad Gateway 网关错误
503 Service Unavailable 服务暂时不可用
504 Gateway Timeout 网关超时

D. 网络调试工具推荐

D.1 抓包工具
工具 平台 特点
Wireshark 全平台 功能强大,支持所有协议
Fiddler Windows HTTP/HTTPS调试专用
Charles 全平台 移动开发友好
mitmproxy 全平台 命令行,可编程
D.2 测试工具
工具 用途
Postman API测试、HTTP请求
curl 命令行HTTP客户端
netcat (nc) TCP/UDP测试
tcpdump 命令行抓包
D.3 在线服务
服务 地址 用途
httpbin httpbin.org HTTP测试
WebSocket Echo echo.websocket.org WebSocket测试
Public APIs github.com/public-apis API列表
D.4 Qt调试技巧
cpp 复制代码
// 启用网络调试日志
qputenv("QT_LOGGING_RULES", "qt.network.*=true");

// SSL调试
qputenv("QT_LOGGING_RULES", "qt.network.ssl=true");

// 详细日志
QLoggingCategory::setFilterRules("qt.network.ssl.warning=true\n"
                                  "qt.network.ssl.debug=true");

E. 参考资源

E.1 官方文档
E.2 示例代码
E.3 协议规范

总结

本文档全面介绍了Qt网络编程的各个方面:

知识体系回顾

复制代码
Qt网络编程
├── 第一章:网络编程基础
│   └── 网络模型、TCP/UDP协议、Qt网络模块概览
├── 第二章:QTcpSocket详解
│   └── TCP客户端/服务端、数据帧设计、多客户端处理
├── 第三章:QUdpSocket详解
│   └── UDP通信、广播、组播、可靠性增强
├── 第四章:HTTP编程
│   └── GET/POST、RESTful API、文件传输、SSL配置
├── 第五章:WebSocket通信
│   └── 客户端/服务端、心跳机制、实时应用
├── 第六章:最佳实践
│   └── 错误处理、超时、重试、性能、安全
├── 第七章:实战项目
│   └── TCP聊天、HTTP下载器、局域网发现
└── 第八章:进阶主题
    └── DNS解析、代理、网络监测、Qt6新特性

核心要点

协议 使用场景 关键类
TCP 可靠传输(聊天、文件) QTcpServer, QTcpSocket
UDP 实时传输(视频、发现) QUdpSocket
HTTP Web API调用 QNetworkAccessManager
WebSocket 双向实时通信 QWebSocket, QWebSocketServer

开发建议

  1. 始终使用异步模式 - 通过信号槽处理网络事件
  2. 妥善处理错误 - 每个网络操作都可能失败
  3. 设置合理超时 - 避免请求无限等待
  4. 实现重试机制 - 提高应用健壮性
  5. 注意安全性 - 使用HTTPS,保护敏感数据
  6. 测试各种场景 - 弱网、断网、高延迟

文档版本 : 1.0
适用Qt版本 : Qt 5.15+ / Qt 6.x
最后更新: 2026年1月

相关推荐
云边云科技_云网融合3 小时前
AIoT智能物联网平台:架构解析与边缘应用新图景
大数据·网络·人工智能·安全
若风的雨3 小时前
NCCL 怎么解决rdma 网卡本地send的时候需要对端recv要准备好的问题,或者mpi 怎么解决的?
网络
wkd_0073 小时前
【Qt | QTableWidget】QTableWidget 类的详细解析与代码实践
开发语言·qt·qtablewidget·qt5.12.12·qt表格
浩浩测试一下4 小时前
DDOS 应急响应Linux防火墙 Iptable 使用方式方法
linux·网络·安全·web安全·网络安全·系统安全·ddos
2501_915918414 小时前
HTTPS 代理失效,启用双向认证(mTLS)的 iOS 应用网络怎么抓包调试
android·网络·ios·小程序·https·uni-app·iphone
8K超高清4 小时前
回望2025,纷呈超清智能科技影像世界
网络·人工智能·科技·数码相机·智能硬件
2501_941982054 小时前
企微非官方API开发:RPA与协议结合的混合驱动实现
网络·企业微信·rpa
残梦53144 小时前
Qt6.9.1起一个图片服务器(支持前端跨域请求,不支持上传,可扩展)
运维·服务器·开发语言·c++·qt
mengzhi啊5 小时前
QT的语言家使用方法示范
qt