From 0dd93c113b72df3605f3ae94e33eaadf2e42cd1b Mon Sep 17 00:00:00 2001 From: donghualin Date: Thu, 29 Jan 2026 16:31:22 +0800 Subject: [PATCH] chore: replace the connectivity check type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit used libcurl to check the connectivity Log:replace the connectivity check type Influence: 任务栏图标显示的状态是否正常 PMS: BUG-331389 --- config/org.deepin.dde.network.json | 37 +- debian/control | 3 +- network-service-plugin/CMakeLists.txt | 3 + .../src/system/connectivitychecker.cpp | 334 ++++++++++-------- .../src/system/connectivitychecker.h | 58 ++- .../src/system/connectivityprocesser.cpp | 7 +- .../src/system/connectivityprocesser.h | 5 +- .../src/system/systemcontainer.cpp | 4 +- .../src/system/systemcontainer.h | 2 +- .../src/utils/httpmanager.cpp | 193 ++++++++++ .../src/utils/httpmanager.h | 59 ++++ .../src/utils/settingconfig.cpp | 32 +- .../src/utils/settingconfig.h | 8 +- 13 files changed, 558 insertions(+), 187 deletions(-) mode change 100644 => 100755 network-service-plugin/src/system/connectivitychecker.cpp mode change 100644 => 100755 network-service-plugin/src/system/connectivitychecker.h create mode 100755 network-service-plugin/src/utils/httpmanager.cpp create mode 100755 network-service-plugin/src/utils/httpmanager.h diff --git a/config/org.deepin.dde.network.json b/config/org.deepin.dde.network.json index fbb0d6ce..d2524856 100644 --- a/config/org.deepin.dde.network.json +++ b/config/org.deepin.dde.network.json @@ -138,7 +138,7 @@ "visibility": "private" }, "ConnectivityCheckInterval": { - "value": 30, + "value": 150, "serial": 0, "flags": [], "name": "ConnectivityCheckInterval", @@ -307,6 +307,39 @@ "description": "if network connect failure: true:do not notify message, false:notify message", "permissions": "readwrite", "visibility": "private" - } + }, + "httpRequestTimeout":{ + "value": 15, + "serial": 0, + "flags":["global"], + "name":"httpRequestTimeout", + "name[zh_CN]":"HTTP请求超时时间", + "description[zh_CN]":"HTTP请求超时时间(单位:秒),默认15秒", + "description":"HTTP request timeout in seconds, default 15 seconds", + "permissions":"readwrite", + "visibility":"private" + }, + "httpConnectTimeout":{ + "value": 10, + "serial": 0, + "flags":["global"], + "name":"httpConnectTimeout", + "name[zh_CN]":"HTTP连接超时时间", + "description[zh_CN]":"HTTP连接超时时间(单位:秒),默认10秒", + "description":"HTTP connection timeout in seconds, default 10 seconds", + "permissions":"readwrite", + "visibility":"private" + }, + "ConnectivityIntervalWhenLimit":{ + "value":30, + "serial":0, + "flags":["global"], + "name":"ConnectivityIntervalWhenLimit", + "name[zh_CN]":"网络不通的时候检测网络联通性的时间间隔(单位:秒)", + "description[zh_CN]":"网络不通的时候检测网络联通性的时间间隔(单位:秒)", + "description":"the span to check connectivity(unit:second) when limit", + "permissions":"readwrite", + "visibility":"private" + } } } diff --git a/debian/control b/debian/control index 479d0828..927c5cd5 100644 --- a/debian/control +++ b/debian/control @@ -17,7 +17,8 @@ Build-Depends: debhelper-compat (= 12), libdtk6core-dev, libdtk6widget-dev, libgsettings-qt6-dev, - libkf6networkmanagerqt-dev + libkf6networkmanagerqt-dev, + libcurl4-openssl-dev Rules-Requires-Root: no Standards-Version: 4.5.0 Homepage: https://gerrit.uniontech.com/admin/repos/dde-network-core diff --git a/network-service-plugin/CMakeLists.txt b/network-service-plugin/CMakeLists.txt index 92e65166..bb60fee7 100644 --- a/network-service-plugin/CMakeLists.txt +++ b/network-service-plugin/CMakeLists.txt @@ -29,6 +29,7 @@ set(CMAKE_PREFER_PTHREAD_FLAG ON) find_package(Qt${QT_NS} COMPONENTS Core Widgets DBus Network LinguistTools REQUIRED) find_package(KF${QT_NS}NetworkManagerQt REQUIRED) find_package(${DTK_NS} COMPONENTS Core REQUIRED) +find_package(CURL REQUIRED) pkg_check_modules(LibNM REQUIRED IMPORTED_TARGET libnm) pkg_check_modules(QGSettings REQUIRED gsettings-qt6) @@ -44,6 +45,7 @@ target_include_directories(${BIN_NAME} PUBLIC KF${QT_NS}::NetworkManagerQt ${DTK_NS}::Core ${QGSettings_INCLUDE_DIRS} + ${CURL_INCLUDE_DIRS} src src/system src/session @@ -61,6 +63,7 @@ target_link_libraries(${BIN_NAME} PRIVATE KF${QT_NS}::NetworkManagerQt ${DTK_NS}::Core ${QGSettings_LIBRARIES} + ${CURL_LIBRARIES} ) if (CMAKE_BUILD_TYPE STREQUAL "Debug") diff --git a/network-service-plugin/src/system/connectivitychecker.cpp b/network-service-plugin/src/system/connectivitychecker.cpp old mode 100644 new mode 100755 index dd65ec8e..c876944c --- a/network-service-plugin/src/system/connectivitychecker.cpp +++ b/network-service-plugin/src/system/connectivitychecker.cpp @@ -1,53 +1,114 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later #include "connectivitychecker.h" #include "settingconfig.h" -#include "systemipconfilct.h" +#include "httpmanager.h" #include #include -#include +#include #include -#include - -// 当没有进行配置的时候, 则访问我们官网 -static const QStringList CheckUrls{ - "https://www.uniontech.com", -}; +#include using namespace network::systemservice; ConnectivityChecker::ConnectivityChecker(QObject *parent) : QObject(parent) { + qRegisterMetaType("network::service::Connectivity"); +} + +LocalConnectionvityChecker::LocalConnectionvityChecker(QObject *parent) + : ConnectivityChecker(parent) + , m_statusChecker(new StatusChecker) + , m_connectivity(network::service::Connectivity::Unknownconnectivity) + , m_thread(new QThread) +{ + m_statusChecker->moveToThread(m_thread); + connect(m_statusChecker, &StatusChecker::portalDetected, this, &LocalConnectionvityChecker::onPortalDetected); + connect(m_statusChecker, &StatusChecker::connectivityChanged, this, &LocalConnectionvityChecker::onConnectivityChanged); + m_thread->start(); + QMetaObject::invokeMethod(m_statusChecker, &StatusChecker::initConnectivityChecker, Qt::QueuedConnection); } -ConnectivityChecker::~ConnectivityChecker() { } +LocalConnectionvityChecker::~LocalConnectionvityChecker() +{ + m_statusChecker->stop(); + m_thread->quit(); + m_thread->wait(); + m_statusChecker->deleteLater(); + m_thread->deleteLater(); +} + +network::service::Connectivity LocalConnectionvityChecker::connectivity() const +{ + return m_connectivity; +} + +QString LocalConnectionvityChecker::portalUrl() const +{ + NetworkManager::ActiveConnection::Ptr primaryConnection = NetworkManager::primaryConnection(); + if (!primaryConnection.isNull() && primaryConnection->connection()->uuid() != m_statusChecker->detectionConnectionId()) { + return QString(); + } + return m_portalUrl; +} + +void LocalConnectionvityChecker::checkConnectivity() +{ + // m_statusChecker 在单独的线程中执行,因此调用QMetaObject::invokeMethod方法让其在所属的线程中来执行 + QMetaObject::invokeMethod(m_statusChecker, &StatusChecker::checkConnectivity, Qt::QueuedConnection); +} + +void LocalConnectionvityChecker::onPortalDetected(const QString &portalUrl) +{ + if (m_portalUrl == portalUrl) + return; + + m_portalUrl = portalUrl; + emit portalDetected(m_portalUrl); +} + +void LocalConnectionvityChecker::onConnectivityChanged(network::service::Connectivity connectivity) +{ + qCInfo(DSM) << "Connectivity changed, incomming: " << static_cast(connectivity) << ", current: " << static_cast(m_connectivity); + if (m_connectivity == connectivity) + return; + + m_connectivity = connectivity; + emit connectivityChanged(connectivity); +} // 通过本地检测网络连通性 -LocalConnectionvityChecker::LocalConnectionvityChecker(SystemIPConflict *ipHandler, QObject *parent) - : ConnectivityChecker(parent) - , m_checkTimer(new QTimer) - , m_timer(new QTimer) +StatusChecker::StatusChecker(QObject *parent) + : QObject(parent) + , m_checkTimer(new QTimer(this)) + , m_timer(new QTimer(this)) + , m_pendingCheckTimer(new QTimer(this)) + , m_pendingCheck(false) , m_connectivity(network::service::Connectivity::Unknownconnectivity) , m_checkCount(0) - , m_ipConfilctHandler(ipHandler) - , m_lastOpenUrlTime(0) + , m_isStop(false) { - initConnectivityChecker(); - connect(SettingConfig::instance(), &SettingConfig::connectivityCheckIntervalChanged, this, [this](int interval) { - if (interval < 10000) { - interval = 10000; - } - m_checkTimer->setInterval(interval); + qRegisterMetaType("NetworkManager::ActiveConnection::State"); + qRegisterMetaType("NetworkManager::Device::State"); + qRegisterMetaType("NetworkManager::Device::StateChangeReason"); + + // 避免短时间内频繁请求网址,1s内的多次请求都是无意义的 + m_pendingCheckTimer->setSingleShot(true); + m_pendingCheckTimer->setInterval(1000); // 1秒防抖 + connect(m_pendingCheckTimer, &QTimer::timeout, this, [this] { + if (m_pendingCheck) + realStartCheck(); + m_pendingCheck = false; }); } -LocalConnectionvityChecker::~LocalConnectionvityChecker() +StatusChecker::~StatusChecker() { for (QMetaObject::Connection connection : m_checkerConnection) { disconnect(connection); @@ -63,36 +124,48 @@ LocalConnectionvityChecker::~LocalConnectionvityChecker() m_timer->deleteLater(); m_timer = nullptr; - clearProcess(); + if (m_pendingCheckTimer->isActive()) + m_pendingCheckTimer->stop(); + m_pendingCheckTimer->deleteLater(); + m_pendingCheckTimer = nullptr; } -network::service::Connectivity LocalConnectionvityChecker::connectivity() const +network::service::Connectivity StatusChecker::connectivity() const { return m_connectivity; } -QString LocalConnectionvityChecker::portalUrl() const +QString StatusChecker::portalUrl() const { return m_portalUrl; } -void LocalConnectionvityChecker::checkConnectivity() +void StatusChecker::checkConnectivity() { + qCDebug(DSM) << "Check connectivity"; m_checkCount = 0; initDefaultConnectivity(); - if (!m_timer->isActive()) { + if (m_timer->isActive()) { + startCheck(); + } else { m_timer->start(); } } -void LocalConnectionvityChecker::initDeviceConnect(const QList> &devices) +QString StatusChecker::detectionConnectionId() const +{ + return m_primaryId; +} + +void StatusChecker::initDeviceConnect(const QList> &devices) { for (const QSharedPointer &device : devices) { if (device.isNull()) continue; - m_checkerConnection << connect(device.data(), &NetworkManager::Device::stateChanged, this, &LocalConnectionvityChecker::startCheck, Qt::UniqueConnection); + m_checkerConnection << connect(device.data(), &NetworkManager::Device::stateChanged, this, &StatusChecker::startCheck, Qt::UniqueConnection); m_checkerConnection << connect(device.data(), &NetworkManager::Device::activeConnectionChanged, this, [device, this] { + qCInfo(DSM) << "Device active connection changed"; m_checkCount = 0; initDefaultConnectivity(); NetworkManager::ActiveConnection::Ptr activeConnection = device->activeConnection(); @@ -103,24 +176,18 @@ void LocalConnectionvityChecker::initDeviceConnect(const QListactiveConnection()); } - connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionsChanged, this, &LocalConnectionvityChecker::onActiveConnectionChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionsChanged, this, &StatusChecker::onActiveConnectionChanged); } -void LocalConnectionvityChecker::initConnectivityChecker() +void StatusChecker::initConnectivityChecker() { - connect(SettingConfig::instance(), &SettingConfig::checkUrlsChanged, this, &LocalConnectionvityChecker::onUpdateUrls); + connect(SettingConfig::instance(), &SettingConfig::checkUrlsChanged, this, &StatusChecker::onUpdateUrls); onUpdateUrls(SettingConfig::instance()->networkCheckerUrls()); // 定期检查网络的状态 - int interval = SettingConfig::instance()->connectivityCheckInterval() * 1000; - if (interval < 10000) { - interval = 10000; - } - m_checkTimer->setInterval(interval); - m_checkerConnection << connect(m_checkTimer, &QTimer::timeout, this, &LocalConnectionvityChecker::startCheck, Qt::UniqueConnection); - m_checkTimer->start(); + m_checkerConnection << connect(m_checkTimer, &QTimer::timeout, this, &StatusChecker::startCheck, Qt::UniqueConnection); // 这个定时器用于在设备状态发生变化后开启,每间隔2秒做一次网络检测,连续做8次然后再停下,目的是为了在设备状态发生变化的一瞬间网络检测不准确,因此做了这个机制,这样的话保证了在设备状态变化后一定时间内状态始终正确 m_timer->setInterval(2000); - m_checkerConnection << connect(m_timer, &QTimer::timeout, this, &LocalConnectionvityChecker::startCheck, Qt::UniqueConnection); + m_checkerConnection << connect(m_timer, &QTimer::timeout, this, &StatusChecker::startCheck, Qt::UniqueConnection); m_checkerConnection << connect(m_timer, &QTimer::timeout, this, [this] { if (m_checkCount >= 8) { m_checkCount = 0; @@ -139,26 +206,15 @@ void LocalConnectionvityChecker::initConnectivityChecker() initDeviceConnect(QList>() << NetworkManager::findNetworkInterface(uni)); }); // 第一次进来的时候执行一次网络状态的检测 - QMetaObject::invokeMethod(this, &LocalConnectionvityChecker::startCheck, Qt::QueuedConnection); + startCheck(); } -network::service::Connectivity LocalConnectionvityChecker::getDefaultLimitConnectivity() const +void StatusChecker::stop() { - NetworkManager::Device::List devices = NetworkManager::networkInterfaces(); - for (NetworkManager::Device::Ptr device : devices) { - if (device->state() != NetworkManager::Device::State::Activated) - continue; - - if (m_connectivity == network::service::Connectivity::Portal) - return network::service::Connectivity::Portal; - - return network::service::Connectivity::Limited; - } - - return network::service::Connectivity::Noconnectivity; + m_isStop = true; } -void LocalConnectionvityChecker::onUpdataActiveState(const QSharedPointer &networks) +void StatusChecker::onUpdataActiveState(const QSharedPointer &networks) { if (networks.isNull()) return; @@ -170,93 +226,50 @@ void LocalConnectionvityChecker::onUpdataActiveState(const QSharedPointerstart("curl", { "-Li", "--connect-timeout", "5", it.key() }); - QTimer::singleShot(10000, process, &QProcess::terminate); - } -} - -void LocalConnectionvityChecker::onFinished(int exitCode) -{ - QProcess *process = qobject_cast(sender()); - if (!process) + qCDebug(DSM) << "Start check connectivity, real start check"; + if (m_isStop) { + qCDebug(DSM) << "Stop check connectivity"; return; - for (auto it = m_checkUrls.begin(); it != m_checkUrls.end(); ++it) { - if (it.value() == process) { - qCDebug(DSM) << "check Url:" << it.key() << "exitCode:" << exitCode; - if (exitCode == 0) { - QString output = QString::fromLocal8Bit(process->readAllStandardOutput()); - QStringList lines = output.split('\n'); - int httpCode = 0; - QString portalUrl; - for (auto &&line : lines) { - if (line.startsWith("HTTP") && httpCode == 0) { - QStringList httpOut = line.split(' '); - if (httpOut.size() >= 2) { - httpCode = httpOut.at(1).at(0).digitValue(); - } - } else if (line.startsWith("Location: ")) { - portalUrl = line.split(' ').at(1).trimmed(); - } else if (httpCode == 2 && line.contains("top.self.location.href")) { - portalUrl = line.trimmed(); - portalUrl = portalUrl.replace("", ""); - portalUrl = portalUrl.replace("top.self.location.href=", ""); - portalUrl = portalUrl.replace("'", ""); - } - } - qCDebug(DSM) << "code:" << httpCode << "portalUrl:" << portalUrl; - switch (httpCode) { - case 2: // 2xx(成功状态码):表示请求已经被成功接收、理解、并处理 - case 3: // 3xx(重定向状态码):表示请求需要进一步操作,以完成请求 - case 5: // 5xx(服务器错误状态码):表示服务器处理请求时出现了错误 - setPortalUrl(portalUrl); - setConnectivity(portalUrl.isEmpty() ? network::service::Connectivity::Full : network::service::Connectivity::Portal); - clearProcess(); - return; - default: - break; - } - } - it.value() = nullptr; - process->deleteLater(); - process = nullptr; - break; - } } - if (process) { // process未被置空表示未找到 - process->deleteLater(); - return; + + NetworkManager::ActiveConnection::Ptr pConnection = NetworkManager::primaryConnection(); + if (!pConnection.isNull()) { + m_primaryId = pConnection->connection()->uuid(); } - bool isFinished = true; - for (auto &&proc : m_checkUrls) { - if (proc) { // 返回的都已置空,还有没返回的则等待 - isFinished = false; + bool networkIsOk = false; + for (const QString &url : m_checkUrls) { + network::service::HttpManager http; + network::service::HttpReply *httpReply = http.get(url); + if (m_isStop) { + qCDebug(DSM) << "Stop check connectivity"; break; } + int httpCode = httpReply->httpCode(); + if (httpCode == 0) { + qCWarning(DSM) << "Nework is unreachabel:" << url << httpReply->errorMessage(); + continue; + } + + QString portalUrl = httpReply->portal(); + networkIsOk = true; + qCDebug(DSM) << "Http reply code:" << httpCode << ", portal url:" << portalUrl; + if (portalUrl.isEmpty()) { + // if the portal is empty, I think it ok + setConnectivity(network::service::Connectivity::Full); + } else { + setConnectivity(network::service::Connectivity::Portal); + } + setPortalUrl(portalUrl); + break; } - if (isFinished) { // 都处理完成还是没连上网,则为不可上网 + if (!m_isStop && !networkIsOk) { NetworkManager::Device::List devices = NetworkManager::networkInterfaces(); int disconnectCount = 0; for (NetworkManager::Device::Ptr device : devices) { @@ -265,6 +278,7 @@ void LocalConnectionvityChecker::onFinished(int exitCode) disconnectCount++; } } + qCDebug(DSM) << "Network is unreachabel, disconnect count:" << disconnectCount; setPortalUrl(QString()); if (disconnectCount == devices.size()) { // 如果所有的网络设备都断开了,就默认让其变为断开的状态 @@ -275,20 +289,25 @@ void LocalConnectionvityChecker::onFinished(int exitCode) } } -void LocalConnectionvityChecker::clearProcess() +// 如果当前定时器没有激活,则立即执行请求,并把后续1s内的请求合并为一次请求 +// 不是一个完美的方案,但是可以大大减少请求的次数(特别是待机唤醒的场景)。 +void StatusChecker::startCheck() { - for (auto it = m_checkUrls.begin(); it != m_checkUrls.end(); ++it) { - if (auto process = it.value()) { - it.value() = nullptr; - if (process->state() != QProcess::NotRunning) { - process->terminate(); - } - } + qCDebug(DSM) << "Start check connectivity, sender:" + << (sender() ? sender()->metaObject()->className() : "direct"); + if (!m_pendingCheckTimer->isActive()) { + realStartCheck(); + m_pendingCheck = false; + m_pendingCheckTimer->start(); + } else { + qCDebug(DSM) << "Debounce timer is active, mark pending check"; + m_pendingCheck = true; } } -void LocalConnectionvityChecker::onActiveConnectionChanged() +void StatusChecker::onActiveConnectionChanged() { + qCInfo(DSM) << "Active connection changed"; // VPN changed NetworkManager::ActiveConnection::List activeVpnConnections; NetworkManager::ActiveConnection::List activeConnections = NetworkManager::activeConnections(); @@ -311,34 +330,39 @@ void LocalConnectionvityChecker::onActiveConnectionChanged() } } -void LocalConnectionvityChecker::setConnectivity(const network::service::Connectivity &connectivity) +void StatusChecker::setConnectivity(const network::service::Connectivity &connectivity) { - if (m_connectivity == connectivity) - return; - qCDebug(DSM) << "connectivity changed" << static_cast(connectivity); m_connectivity = connectivity; // 更新每个设备的联通性 emit connectivityChanged(m_connectivity); + + // 根据实际情况,如果网络不通,就30秒刷新一次,网络通了,300秒刷新一次 + int interval = (connectivity == network::service::Connectivity::Full ? + SettingConfig::instance()->connectivityCheckInterval() * 1000 : + SettingConfig::instance()->connectivityIntervalWhenLimit() * 1000); + qCDebug(DSM) << "check interval" << m_checkTimer->interval() << ",set interval" << interval; + if (m_checkTimer->interval() != interval) { + if (m_checkTimer->isActive()) + m_checkTimer->stop(); + + m_checkTimer->setInterval(interval); + m_checkTimer->start(); + } } -void LocalConnectionvityChecker::setPortalUrl(const QString &portalUrl) +void StatusChecker::setPortalUrl(const QString &portalUrl) { + qCDebug(DSM) << "pre url:" << m_portalUrl << "new url:" << portalUrl; if (m_portalUrl == portalUrl) return; // 每次检测时返回的认证连接可能不同,连接时会多次打开认证网站 - qint64 tmpTime = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000; - if (tmpTime - m_lastOpenUrlTime < 60) { - return; - } - m_lastOpenUrlTime = tmpTime; - m_portalUrl = portalUrl; emit portalDetected(m_portalUrl); } -void LocalConnectionvityChecker::initDefaultConnectivity() +void StatusChecker::initDefaultConnectivity() { // 如果上一次检测的是网络连接受限或者需要认证,则无需设置初始值,否则会出现在使用过程种,本来是无法上网的状态,而设置了错误的初始值导致状态错误 // 这个函数是为了在断开所有的连接后,再开启连接后设置其初始值 @@ -377,8 +401,6 @@ NMConnectionvityChecker::NMConnectionvityChecker(QObject *parent) initConnection(); } -NMConnectionvityChecker::~NMConnectionvityChecker() { } - static network::service::Connectivity convertConnectivity(const NetworkManager::Connectivity &connectivity) { switch (connectivity) { diff --git a/network-service-plugin/src/system/connectivitychecker.h b/network-service-plugin/src/system/connectivitychecker.h old mode 100644 new mode 100755 index 2ca507fc..ddc01e08 --- a/network-service-plugin/src/system/connectivitychecker.h +++ b/network-service-plugin/src/system/connectivitychecker.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -7,22 +7,19 @@ #include "constants.h" -#include #include class QTimer; -class QProcess; namespace NetworkManager { class Device; -class WirelessNetwork; class ActiveConnection; } // namespace NetworkManager namespace network { namespace systemservice { -class SystemIPConflict; +class StatusChecker; // 网络连通性的检测 class ConnectivityChecker : public QObject @@ -31,7 +28,7 @@ class ConnectivityChecker : public QObject public: ConnectivityChecker(QObject *parent = Q_NULLPTR); - virtual ~ConnectivityChecker(); + virtual ~ConnectivityChecker() = default; virtual network::service::Connectivity connectivity() const = 0; @@ -48,7 +45,7 @@ class LocalConnectionvityChecker : public ConnectivityChecker Q_OBJECT public: - LocalConnectionvityChecker(SystemIPConflict *ipHandler, QObject *parent = Q_NULLPTR); + explicit LocalConnectionvityChecker(QObject *parent = Q_NULLPTR); ~LocalConnectionvityChecker() override; network::service::Connectivity connectivity() const override; @@ -58,10 +55,38 @@ class LocalConnectionvityChecker : public ConnectivityChecker signals: void portalDetected(const QString &); +private slots: + void onPortalDetected(const QString &portalUrl); + void onConnectivityChanged(network::service::Connectivity connectivity); + private: - void initDeviceConnect(const QList> &devices); + StatusChecker *m_statusChecker; + QString m_portalUrl; + network::service::Connectivity m_connectivity; + QThread *m_thread; +}; + +class StatusChecker : public QObject +{ + Q_OBJECT + +public: + explicit StatusChecker(QObject *parent = Q_NULLPTR); + ~StatusChecker() override; void initConnectivityChecker(); - network::service::Connectivity getDefaultLimitConnectivity() const; + void stop(); + + network::service::Connectivity connectivity() const; + QString portalUrl() const; + void checkConnectivity(); + QString detectionConnectionId() const; + +signals: + void portalDetected(const QString &); + void connectivityChanged(const network::service::Connectivity &); + +private: + void initDeviceConnect(const QList> &devices); void setConnectivity(const network::service::Connectivity &connectivity); void setPortalUrl(const QString &portalUrl); void initDefaultConnectivity(); @@ -70,20 +95,21 @@ private slots: void onUpdataActiveState(const QSharedPointer &networks); void onUpdateUrls(const QStringList &urls); void startCheck(); - void onFinished(int exitCode); - void clearProcess(); + void realStartCheck(); void onActiveConnectionChanged(); private: QTimer *m_checkTimer; QTimer *m_timer; + QTimer *m_pendingCheckTimer; + bool m_pendingCheck; QList m_checkerConnection; network::service::Connectivity m_connectivity; int m_checkCount; - SystemIPConflict *m_ipConfilctHandler; QString m_portalUrl; - QMap m_checkUrls; - qint64 m_lastOpenUrlTime; + QStringList m_checkUrls; + bool m_isStop; + QString m_primaryId; }; class NMConnectionvityChecker : public ConnectivityChecker @@ -91,8 +117,8 @@ class NMConnectionvityChecker : public ConnectivityChecker Q_OBJECT public: - NMConnectionvityChecker(QObject *parent = Q_NULLPTR); - ~NMConnectionvityChecker() override; + explicit NMConnectionvityChecker(QObject *parent = Q_NULLPTR); + ~NMConnectionvityChecker() override = default; network::service::Connectivity connectivity() const override; private: diff --git a/network-service-plugin/src/system/connectivityprocesser.cpp b/network-service-plugin/src/system/connectivityprocesser.cpp index e50a23e6..6d0528b5 100644 --- a/network-service-plugin/src/system/connectivityprocesser.cpp +++ b/network-service-plugin/src/system/connectivityprocesser.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2018 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -10,9 +10,8 @@ using namespace network::systemservice; -ConnectivityProcesser::ConnectivityProcesser(SystemIPConflict *ipConflict, QObject *parent) +ConnectivityProcesser::ConnectivityProcesser(QObject *parent) : QObject(parent) - , m_ipConflictHandler(ipConflict) { SettingConfig *config = SettingConfig::instance(); connect(config, &SettingConfig::enableConnectivityChanged, this, &ConnectivityProcesser::onEnableConnectivityChanged); @@ -43,7 +42,7 @@ ConnectivityChecker *ConnectivityProcesser::createConnectivityChecker(bool enabl ConnectivityChecker *checker = nullptr; if (enableConnectivity) { qCDebug(DSM) << "uses local connectivity checker"; - LocalConnectionvityChecker *localChecker = new LocalConnectionvityChecker(m_ipConflictHandler, this); + LocalConnectionvityChecker *localChecker = new LocalConnectionvityChecker(this); connect(localChecker, &LocalConnectionvityChecker::portalDetected, this, &ConnectivityProcesser::portalDetected); checker = localChecker; } else { diff --git a/network-service-plugin/src/system/connectivityprocesser.h b/network-service-plugin/src/system/connectivityprocesser.h index bee9aa19..60378318 100644 --- a/network-service-plugin/src/system/connectivityprocesser.h +++ b/network-service-plugin/src/system/connectivityprocesser.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2018 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -22,7 +22,7 @@ class ConnectivityProcesser : public QObject Q_OBJECT public: - explicit ConnectivityProcesser(SystemIPConflict *ipConflict, QObject *parent = nullptr); + explicit ConnectivityProcesser(QObject *parent = nullptr); ~ConnectivityProcesser(); void checkConnectivity(); network::service::Connectivity connectivity() const; @@ -40,7 +40,6 @@ private slots: private: QScopedPointer m_checker; - SystemIPConflict *m_ipConflictHandler; }; } diff --git a/network-service-plugin/src/system/systemcontainer.cpp b/network-service-plugin/src/system/systemcontainer.cpp index 82f85db5..d4ed7c9f 100644 --- a/network-service-plugin/src/system/systemcontainer.cpp +++ b/network-service-plugin/src/system/systemcontainer.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -12,7 +12,7 @@ using namespace network::systemservice; SystemContainer::SystemContainer(QObject *parent) : QObject (parent) , m_ipConflictHandler(new SystemIPConflict(this)) - , m_connectivityHelper(new ConnectivityProcesser(m_ipConflictHandler, this)) + , m_connectivityHelper(new ConnectivityProcesser(this)) { NetworkInitialization::doInit(); } diff --git a/network-service-plugin/src/system/systemcontainer.h b/network-service-plugin/src/system/systemcontainer.h index d9af4456..dd4330df 100644 --- a/network-service-plugin/src/system/systemcontainer.h +++ b/network-service-plugin/src/system/systemcontainer.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later diff --git a/network-service-plugin/src/utils/httpmanager.cpp b/network-service-plugin/src/utils/httpmanager.cpp new file mode 100755 index 00000000..b666ae55 --- /dev/null +++ b/network-service-plugin/src/utils/httpmanager.cpp @@ -0,0 +1,193 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "httpmanager.h" +#include "constants.h" +#include "settingconfig.h" + +#include +#include + +#include +#include +#include +#include + +using namespace network::service; + +namespace network { +namespace service { + +void HttpManager::init() +{ + curl_global_init(CURL_GLOBAL_ALL); +} + +void HttpManager::unInit() +{ + curl_global_cleanup(); +} + +HttpManager::HttpManager(QObject *parent) + : QObject (parent) +{ +} + +static ulong write_data(char *data, size_t size, size_t nmemb, std::string *buffer) +{ + size_t total_size = size * nmemb; + if (buffer) { + buffer->append(data, total_size); + } + return total_size; +} + +// 为底层 socket 设置收发超时,避免内核层面 read/write 长时间阻塞 +struct CurlSockoptContext { + long rwTimeoutSec { 10 }; +}; + +static int sockopt_callback(void *clientp, curl_socket_t sockfd, curlsocktype /*purpose*/) +{ + const CurlSockoptContext *ctx = static_cast(clientp); + long timeoutSec = (ctx && ctx->rwTimeoutSec > 0) ? ctx->rwTimeoutSec : 10L; + + timeval tv; + tv.tv_sec = static_cast(timeoutSec); + tv.tv_usec = 0; + + // 忽略 setsockopt 失败,继续走 libcurl 自有超时;这里主要作为额外保险 + (void)setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(tv)); + (void)setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&tv), sizeof(tv)); + + return CURL_SOCKOPT_OK; +} + +HttpReply *HttpManager::get(const QString &url) +{ + HttpReply *reply = new HttpReply(this); + CURL *curl = curl_easy_init(); + if (!curl) { + qCWarning(DSM) << "Curl initialization failed for URL" << url; + reply->setErrorMessage("curl easy init failure"); + return reply; + } + + // 请求URL + curl_easy_setopt(curl, CURLOPT_URL, url.toStdString().c_str()); + // 禁用打印日志 + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); + // 当前请求方式设置为Get + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); + // 禁用信号,防止在多线程环境中死锁 + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); + // 设置超时(从配置文件读取) + curl_easy_setopt(curl, CURLOPT_TIMEOUT, static_cast(SettingConfig::instance()->httpRequestTimeout())); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, static_cast(SettingConfig::instance()->httpConnectTimeout())); + // 设置回调函数,当写入头数据的时候,将会调用write_data方法来写入数据 + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); + std::string body_data; + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body_data); + // 需要写入header的数据,如果不设置此项,就不会写入header数据,导致无法解析header(类似 curl -Li命令) + curl_easy_setopt(curl, CURLOPT_HEADER, 1L); + // 跟随重定向 + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + // 设置套接字级别的收发超时,防止底层 read/write 长时间阻塞 + CurlSockoptContext sockCtx; + sockCtx.rwTimeoutSec = static_cast(SettingConfig::instance()->httpRequestTimeout()); + curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback); + curl_easy_setopt(curl, CURLOPT_SOCKOPTDATA, &sockCtx); + // 发送请求 + qCDebug(DSM) << "Send request to" << url; + CURLcode curlRes = curl_easy_perform(curl); + qCDebug(DSM) << "Request finished, ret:" << curlRes; + if (curlRes == CURLE_OK) { + reply->setHeader(QString::fromStdString(body_data)); + } else { + const QString &errorMsg = curl_easy_strerror(curlRes); + qCInfo(DSM) << "Request failed for" << url << ", error message:" << errorMsg; + reply->setErrorMessage(errorMsg); + } + + curl_easy_cleanup(curl); + return reply; +} + +/** + * @brief HttpReply::HttpReply + * @param parent + */ +HttpReply::HttpReply(QObject *parent) + : QObject (parent) + , m_httpCode(0) +{ +} + +void HttpReply::setHeader(const QString &html) +{ + m_httpCode = 0; + m_portalUrl.clear(); + QStringList headerLines = html.split("\n"); + for (const QString &line : headerLines) { + if (line.startsWith("HTTP") && m_httpCode == 0) { + QStringList httpOut = line.split(' '); + if (httpOut.size() >= 2) { + m_httpCode = httpOut.at(1).toInt(); + } + } else if (line.startsWith("Location: ")) { + m_portalUrl = line.split(' ').at(1).trimmed(); + } else if ((m_httpCode >= 200 && m_httpCode < 300)) { + if (line.contains("top.self.location.href")) { + m_portalUrl = line.trimmed(); + m_portalUrl = m_portalUrl.replace("", ""); + m_portalUrl = m_portalUrl.replace("top.self.location.href=", ""); + m_portalUrl = m_portalUrl.replace("'", ""); + } else if (line.startsWith("value(key).toBool(); enableConnectivityChanged(m_enableConnectivity); + } else if (key == QString("ConnectivityIntervalWhenLimit")) { + m_connectivityIntervalWhenLimit = dConfig->value("ConnectivityIntervalWhenLimit").toInt(); } else if (key == QString("ConnectivityCheckInterval")) { m_connectivityCheckInterval = dConfig->value("ConnectivityCheckInterval").toInt() * 1000; emit connectivityCheckIntervalChanged(m_connectivityCheckInterval); @@ -81,6 +98,10 @@ void SettingConfig::onValueChanged(const QString &key) } else if (key == QString("resetWifiOSDEnableTimeout")) { m_resetWifiOSDEnableTimeout = dConfig->value("resetWifiOSDEnableTimeout").toInt(); emit resetWifiOSDEnableTimeoutChanged(m_resetWifiOSDEnableTimeout); + } else if (key == QString("httpRequestTimeout")) { + m_httpRequestTimeout = dConfig->value("httpRequestTimeout").toInt(); + } else if (key == QString("httpConnectTimeout")) { + m_httpConnectTimeout = dConfig->value("httpConnectTimeout").toInt(); } } @@ -108,6 +129,9 @@ SettingConfig::SettingConfig(QObject *parent) if (keys.contains("enableConnectivity")) m_enableConnectivity = dConfig->value("enableConnectivity").toBool(); + if (keys.contains("ConnectivityIntervalWhenLimit")) + m_connectivityIntervalWhenLimit = dConfig->value("ConnectivityIntervalWhenLimit").toInt(); + if (keys.contains("ConnectivityCheckInterval")) m_connectivityCheckInterval = dConfig->value("ConnectivityCheckInterval").toInt(); @@ -126,6 +150,12 @@ SettingConfig::SettingConfig(QObject *parent) if (keys.contains("resetWifiOSDEnableTimeout")) m_resetWifiOSDEnableTimeout = dConfig->value("resetWifiOSDEnableTimeout").toInt(); + if (keys.contains("httpRequestTimeout")) + m_httpRequestTimeout = dConfig->value("httpRequestTimeout").toInt(); + + if (keys.contains("httpConnectTimeout")) + m_httpConnectTimeout = dConfig->value("httpConnectTimeout").toInt(); + m_disableFailureNotify = dConfig->value("disableFailureNotify", false).toBool(); } } diff --git a/network-service-plugin/src/utils/settingconfig.h b/network-service-plugin/src/utils/settingconfig.h index c7331340..4e37ab9b 100644 --- a/network-service-plugin/src/utils/settingconfig.h +++ b/network-service-plugin/src/utils/settingconfig.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -15,6 +15,7 @@ class SettingConfig : public QObject static SettingConfig *instance(); bool reconnectIfIpConflicted() const; bool enableConnectivity() const; + int connectivityIntervalWhenLimit() const; int connectivityCheckInterval() const; QStringList networkCheckerUrls() const; // 网络检测地址,用于检测网络连通性 bool checkPortal() const; // 是否检测网络认证信息 @@ -22,6 +23,8 @@ class SettingConfig : public QObject bool enableAccountNetwork() const; // 是否开启用户私有网络(工银瑞信定制) bool disableFailureNotify() const; // 当网络连接失败后,true:不弹出消息,false:弹出消息 int resetWifiOSDEnableTimeout() const; // 重新显示网络连接OSD超时 + int httpRequestTimeout() const; // HTTP请求超时时间(秒) + int httpConnectTimeout() const; // HTTP连接超时时间(秒) signals: void enableConnectivityChanged(bool); @@ -40,6 +43,7 @@ private slots: private: bool m_reconnectIfIpConflicted; bool m_enableConnectivity; + int m_connectivityIntervalWhenLimit; int m_connectivityCheckInterval; QStringList m_networkUrls; bool m_checkPortal; @@ -47,6 +51,8 @@ private slots: bool m_enableAccountNetwork; bool m_disableFailureNotify; int m_resetWifiOSDEnableTimeout; + int m_httpRequestTimeout; + int m_httpConnectTimeout; }; #endif // SERVICE_H