diff --git a/docs/server-audit.md b/docs/server-audit.md index 724b63f..d585089 100644 --- a/docs/server-audit.md +++ b/docs/server-audit.md @@ -298,10 +298,12 @@ --- -## Phase 4 — Коммуникация с устройствами +## Phase 4 — Коммуникация с устройствами ✅ Выполнена **Цель:** Сделать работу с устройствами отказоустойчивой и неблокирующей. +**Коммит:** `966b764` (ветка `dev`) + ### 4.1 🟠 DeviceScanner — 253 одновременных cURL **Где:** `server/SHServ/Tools/DeviceScanner.php:72-98` diff --git a/server/SHServ/.env.example b/server/SHServ/.env.example index 2f5b2ef..9a0072c 100644 --- a/server/SHServ/.env.example +++ b/server/SHServ/.env.example @@ -12,3 +12,5 @@ DEVICE_IP_RANGE_START=192.168.2.2 DEVICE_IP_RANGE_END=192.168.2.254 +DEVICE_API_CONNECT_TIMEOUT=1 +DEVICE_API_TIMEOUT=5 diff --git a/server/SHServ/Controllers/DevicesRESTAPIController.php b/server/SHServ/Controllers/DevicesRESTAPIController.php index 906620d..0ee2818 100644 --- a/server/SHServ/Controllers/DevicesRESTAPIController.php +++ b/server/SHServ/Controllers/DevicesRESTAPIController.php @@ -399,7 +399,13 @@ return $this -> utils() -> response_error("device_not_found"); } - $device -> device_api() -> reset(); + $result = $device -> device_api() -> reset(); + + if(($result["http_code"] ?? 0) !== 200) { + return $this -> utils() -> response_error("device_request_fail", [], [ + "device_msg" => $result["error"] ?? "" + ]); + } return $this -> utils() -> response_success(); } diff --git a/server/SHServ/RequiredControlScriptsScope.php b/server/SHServ/RequiredControlScriptsScope.php index 965748b..2f124e8 100644 --- a/server/SHServ/RequiredControlScriptsScope.php +++ b/server/SHServ/RequiredControlScriptsScope.php @@ -12,12 +12,6 @@ $this -> add_event_handler("online", function(Device $device, Array $data) { $device -> device_ip = $data["device_ip"]; $device -> connection_status = "active"; - - $device_info = $device -> device_api() -> get_about(); - if($device_info and isset($device_info["data"]) and $device_info["data"]) { - $device -> firmware_version = $device_info["data"]["firmware_version"]; - } - $device -> update(); }); } diff --git a/server/SHServ/Tools/DeviceAPI/Base.php b/server/SHServ/Tools/DeviceAPI/Base.php index 2c07531..dd23cc7 100644 --- a/server/SHServ/Tools/DeviceAPI/Base.php +++ b/server/SHServ/Tools/DeviceAPI/Base.php @@ -6,6 +6,10 @@ private string $ip_address; private string $base_url; private ?string $token; + private float $connect_timeout; + private float $timeout; + private const MAX_RETRIES = 3; + private const RETRY_BACKOFF_MS = 100; /** * @param string $ip_address IP устройства (без протокола и слеша в конце) @@ -15,6 +19,8 @@ $this->ip_address = $ip_address; $this->base_url = rtrim('http://' . $ip_address, '/'); $this->token = $token; + $this->connect_timeout = FCONF['device_api_connect_timeout'] ?? 1; + $this->timeout = FCONF['device_api_timeout'] ?? 5; } /** @@ -175,15 +181,53 @@ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, - CURLOPT_CONNECTTIMEOUT => 1, - CURLOPT_TIMEOUT => 5, + CURLOPT_CONNECTTIMEOUT => $this->connect_timeout, + CURLOPT_TIMEOUT => $this->timeout, CURLOPT_HTTPHEADER => $headers, ]); - $raw_response = curl_exec($ch); + $attempt = 0; + $raw_response = false; + $error_message = null; + + while ($attempt < self::MAX_RETRIES) { + $raw_response = curl_exec($ch); + + if ($raw_response !== false) { + break; + } + + $error_message = curl_error($ch); + $attempt++; + + if ($attempt >= self::MAX_RETRIES) { + break; + } + + $backoff = self::RETRY_BACKOFF_MS * (2 ** ($attempt - 1)); + usleep($backoff * 1000); + curl_reset($ch); + + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_CONNECTTIMEOUT => $this->connect_timeout, + CURLOPT_TIMEOUT => $this->timeout, + CURLOPT_HTTPHEADER => $headers, + ]); + + if (strtoupper($method) === 'POST') { + curl_setopt($ch, CURLOPT_POST, true); + if ($body !== null) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $json_body); + } + } else { + curl_setopt($ch, CURLOPT_HTTPGET, true); + } + } if ($raw_response === false) { - $error_message = curl_error($ch); curl_close($ch); return [ @@ -191,7 +235,7 @@ 'headers' => [], 'raw' => null, 'data' => null, - 'error' => $error_message, + 'error' => $error_message ?? curl_error($ch), ]; } diff --git a/server/SHServ/Tools/DeviceScanner.php b/server/SHServ/Tools/DeviceScanner.php index 4c86ea3..9817e7d 100644 --- a/server/SHServ/Tools/DeviceScanner.php +++ b/server/SHServ/Tools/DeviceScanner.php @@ -66,10 +66,28 @@ return $ips; } + private const BATCH_SIZE = 32; + /** - * Параллельное сканирование IP с curl_multi. + * Параллельное сканирование IP с curl_multi и ограничением на размер батча. */ private function scan_ips(array $ips, int $port, float $timeout): array { + $all_devices = []; + + $batches = array_chunk($ips, self::BATCH_SIZE); + + foreach ($batches as $batch) { + $batch_devices = $this -> scan_batch($batch, $port, $timeout); + $all_devices = array_merge($all_devices, $batch_devices); + } + + return $all_devices; + } + + /** + * Сканирует один батч IP через curl_multi. + */ + private function scan_batch(array $ips, int $port, float $timeout): array { $multi = curl_multi_init(); $handles = []; @@ -89,7 +107,6 @@ curl_multi_add_handle($multi, $ch); - // тут больше НЕ приводим $ch к строке $handles[] = [ 'handle' => $ch, 'ip' => $ip, @@ -104,10 +121,8 @@ break; } - // на некоторых системах curl_multi_select может возвращать -1 $select_result = curl_multi_select($multi, 0.1); if ($select_result === -1) { - // маленькая пауза, чтобы не крутить цикл usleep(100000); } } while ($running > 0); diff --git a/server/SHServ/config.php b/server/SHServ/config.php index 8566a43..ecd670f 100644 --- a/server/SHServ/config.php +++ b/server/SHServ/config.php @@ -43,5 +43,7 @@ "device_ip_range" => [ $env['DEVICE_IP_RANGE_START'] ?? "192.168.2.2", $env['DEVICE_IP_RANGE_END'] ?? "192.168.2.254" - ] + ], + "device_api_connect_timeout" => (float)($env['DEVICE_API_CONNECT_TIMEOUT'] ?? "1"), + "device_api_timeout" => (float)($env['DEVICE_API_TIMEOUT'] ?? "5") ];