//! 本机连通性基准面探测(弱网重试的感知依据)。 //! //! 场景: 隧道/电梯等移动网络波动可达分钟级, 固定次数重试必然失败。send 失败时探 //! 基准面区分「本机断网」与「单点故障」: 前者进长等待(自适应间隔探测, 恢复即重试), //! 后者短退避即可。纯 IP TCP 与系统 DNS 解析双测, 任一通过即判在线 //! (DNS 单挂时 IP 直连上游仍可能可用, 双测互为盲区互补)。 //! //! 演进路线(P2, 需求触发再上): 系统网络事件订阅双通道——事件源治接口级变化 //! (WiFi 断开/切蜂窝)零延迟, 本探针治链路级不通(隧道半断/DNS 故障, 系统事件 //! 不触发的盲区)。跨平台无统一事件 crate(Windows=INetworkListManager COM, //! macOS=NWPathMonitor, Linux=netlink), 届时以 NetEventSource trait 抽象, //! 按 devflow 实际部署面(Windows 桌面+Linux 云端)逐平台实现; Linux 服务端 //! 网络稳定, 纯探针已足够。 use std::net::ToSocketAddrs; use std::time::Duration; /// 探测超时(单次解析或建连)。弱网下慢响应不代表断网, 2s 内完成才计可用。 const PROBE_TIMEOUT: Duration = Duration::from_secs(2); /// send 弱网重试总窗口(隧道/电梯级分钟级波动覆盖)。 pub(crate) const SEND_RETRY_WINDOW: Duration = Duration::from_secs(600); /// 脱网确认后的探针间隔表(秒): 首段 5s 快探保即时性(网络抖动常秒级恢复), /// 确认持续脱网后间隔拉长到 30s 封顶——蜂窝 radio 休眠省电, 分钟级隧道场景无感差异。 pub(crate) const OFFLINE_PROBE_SCHEDULE_SECS: &[u64] = &[5, 5, 5, 5, 10, 15, 30]; /// 按脱网轮次取探针间隔(越界取表尾封顶值)。 pub(crate) fn offline_probe_wait(poll_count: usize) -> Duration { let idx = poll_count.saturating_sub(1).min(OFFLINE_PROBE_SCHEDULE_SECS.len() - 1); Duration::from_secs(OFFLINE_PROBE_SCHEDULE_SECS[idx]) } /// IP 直连探针目标(公共 DNS 服务商的 443, 不依赖 DNS)。 const PROBE_IPS: &[&str] = &["223.5.5.5:443", "119.29.29.29:443"]; /// DNS 面探针目标(公共域, 走系统 resolver)。 const PROBE_DNS_HOST: &str = "www.aliyun.com:443"; async fn tcp_alive(addr: &str) -> bool { let target = match addr.to_socket_addrs() { Ok(mut it) => match it.next() { Some(a) => a, None => return false, }, Err(_) => return false, }; tokio::time::timeout(PROBE_TIMEOUT, tokio::net::TcpStream::connect(target)) .await .is_ok() } /// 本机网络基准面是否可用。任一探针通过即在线。 pub(crate) async fn baseline_alive() -> bool { for ip in PROBE_IPS { if tcp_alive(ip).await { return true; } } matches!( tokio::time::timeout(PROBE_TIMEOUT, tokio::net::lookup_host(PROBE_DNS_HOST)).await, Ok(Ok(_)) ) }