ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Unity网络状态检测实战:从internetReachability到Ping主动探测

Unity网络状态检测实战:从internetReachability到Ping主动探测 做Unity项目无论是单机游戏还是联网应用总会遇到一个绕不开的基础需求判断当前设备有没有联网。我最早做这块的时候特别天真以为查一下Application.internetReachability就完事了结果被各种“路由能通但外网不通”“连着Wi-Fi却拿不到数据”的bug折腾到怀疑人生。后来踩过几次坑才把这块从“能用”磨到了“够稳”。这篇文章就来聊聊Unity里判断网络状态的两种核心方式一种是用Unity自带的Application.internetReachability做快速的本机网络接口判断另一种是用Ping指令主动探测目标主机真正验证“能不能连到外网”。两种方式各有各的适用场景大多数项目其实需要把它们结合起来用。这篇内容适合正在做断线重连、登录鉴权、资源热更前检测等联网相关逻辑的开发者参考看完可以直接抄代码也能少踩我踩过的那几个坑。1. 先搞清楚判断网络状态到底要判断什么很多人在这个功能上翻车不是代码写错而是压根没想清楚需求。同样是“判断网络状态”在不同场景下指的可能是完全不同的东西。我先把这句话拆开讲后面写代码才不会绕弯路。1.1 三个层次的网络判断我习惯把网络状态判断分成三个层次第一层本机有没有可用的网络接口。也就是网卡有没有连上路由、Wi-Fi是不是开了这是Application.internetReachability能回答的问题。第二层当前网络能不能访问到目标服务器。接口虽然是通的但路由器没拨号成功、运营商链路异常、目标服务器挂了都会导致“有网但连不上”这需要通过主动探测来验证。第三层网络质量如何。延迟高不高、丢包率大不大、带宽够不够这属于弱网检测的范畴通常要结合业务层的请求耗时、重试次数来做综合分析。如果把这三层对应到实际业务里你会发现不同模块的需求差异很大。比如启动画面的离线提示只需要第一层就够了登录逻辑必须做到第二层否则玩家点在登录按钮上半天没反应体验非常差而实时对战里的断线重连、帧同步质量监控那就得落到第三层。很多刚接触网络检测的开发者容易犯的错误就是拿第一层的能力去硬扛第二层的需求结果线上出了问题还找不到原因。1.2 两种核心方式的能力边界我把Unity里最常用的两种判断方式放在一起打了个表维度Application.internetReachabilityPing/主动探测判断目标本机网络接口目标主机连通性响应速度即时返回几百毫秒到数秒网络开销无会产生实际探测流量能否发现“有网但连不上”不能能实现复杂度低中平台差异小大尤其WebGL从这个表能看出来两种方式不是“谁替代谁”的关系而是互补关系。internetReachability负责快速筛查Ping负责最终确认。后面的章节我会分别把它们的原理、写法和坑讲透。2. 第一种方式Application.internetReachability快速判断这应该是绝大多数Unity开发者接触到的第一个网络判断API。原因很简单不用引任何库不用配任何权限一行代码就能拿到当前状态。2.1 这个API背后到底返回了什么Application.internetReachability返回的是一个NetworkReachability枚举一共三个值NotReachable当前设备没有任何可用的网络连接也就是Wi-Fi没连、蜂窝网络没开或者处于飞行模式。ReachableViaCarrierDataNetwork当前处于移动蜂窝网络4G/5G连接状态。ReachableViaLocalAreaNetwork当前处于Wi-Fi或有线局域网连接状态。注意这个API在部分平台上只是“系统网络接口是否可用”的映射它不会真的发一个包去验证外网通不通。你把它当成“网卡状态”来理解最准确。它连的是路由器还是基站、路由器的外网通不通它一概不管。2.2 最小可用代码与基础封装直接用的代码很简单using UnityEngine; public class QuickNetworkCheck : MonoBehaviour { private void Update() { if (Application.internetReachability NetworkReachability.NotReachable) { Debug.Log(当前无网络); } else if (Application.internetReachability NetworkReachability.ReachableViaCarrierDataNetwork) { Debug.Log(当前使用蜂窝网络); } else { Debug.Log(当前使用Wi-Fi或局域网); } } }但在真实项目里我建议包一层方便以后替换实现也方便给其他模块复用。一个比较通用的封装长这样public enum NetInterfaceType { None, Cellular, WifiOrLan } public static class NetworkStatusUtil { public static NetInterfaceType GetInterfaceType() { switch (Application.internetReachability) { case NetworkReachability.NotReachable: return NetInterfaceType.None; case NetworkReachability.ReachableViaCarrierDataNetwork: return NetInterfaceType.Cellular; default: return NetInterfaceType.WifiOrLan; } } public static bool HasNetworkInterface() { return GetInterfaceType() ! NetInterfaceType.None; } }提示在Android/iOS平台ReachableViaCarrierDataNetwork只在真实插了SIM卡并开启数据流量时才会出现。部分安卓平板没有蜂窝模块永远不会进入这个状态别到时候排查半天以为是代码问题。2.3 这个方案的坑在哪里这个API最明显的坑就是它只告诉你“有没有网卡”不告诉你“能不能上网”。我遇到过最典型的例子就是路由器WAN口没拨上号但设备连着Wi-Fi。这种情况下Application.internetReachability返回的是ReachableViaLocalAreaNetwork看起来一切正常实际上外网完全不通。如果你拿它做登录前的网络校验用户会被卡在登录界面而且你还不知道问题出在哪。另一个坑是它在模拟器上的表现不一致。安卓模拟器里经常出现“本机有网但枚举结果却是NotReachable”的反向情况尤其是早期版本。所以如果你们的测试环境里有模拟器记得提醒QA同学别被这种误报干扰。3. 第二种方式用Ping主动探测目标主机既然internetReachability不够用那就要想办法做真正的连通性验证。最直接的方式就是Ping给目标服务器发一个ICMP回显请求能收到回应说明从本机到目标主机的网络通路基本是通的。3.1 为什么要主动探测从“有网”到“真的能连”拿开车来打比方。internetReachability只告诉你“油箱里有油”Ping则是真正把车发动起来跑一段路确认发动机、变速箱、轮胎都没问题。主动探测能挡掉一大类“看似有网实则不通”的故障运营商拨号失败、DNS解析异常、目标服务器宕机、防火墙策略拦截等。这些故障用internetReachability完全看不出来但Ping能非常明确地反馈结果。3.2 基于UnityEngine.Ping的最小实现Unity引擎自带的Ping类用起来很直接using UnityEngine; using System.Collections; public class PingChecker : MonoBehaviour { public string target 223.5.5.5; [ContextMenu(Ping Now)] public void PingNow() { StartCoroutine(PingCoroutine()); } private IEnumerator PingCoroutine() { Ping ping new Ping(target); float timeout 3f; float elapsed 0f; while (!ping.isDone elapsed timeout) { elapsed Time.deltaTime; yield return null; } if (ping.isDone ping.time 0) { Debug.Log($Ping成功延迟{ping.time}ms); } else { Debug.LogWarning(Ping超时或失败外网不可达); } } }几个关键点说一下。第一构造Ping就会立刻发起探测不需要再手动调别的接口。第二ping.isDone为true说明已经有结果了但结果不一定代表成功。要判断是否真的连通还得看ping.time是否大于等于0失败时这个值会是-1。第三Ping类没有内置超时机制必须自己用协程或Update做超时控制。上面代码里给了3秒超时实际项目里可以根据业务容忍度调整。如果是登录前的检测一般不超过5秒如果是后台的静默探测可以放到8到10秒。第四target最好传IP而不是域名。虽然Ping的构造函数也接受主机名但DNS解析会带来额外延迟而且解析失败时会直接抛异常影响判断结果。所以稳定做法是服务器配置下发的时候同时下发IP和域名优先用IP做连通性探测。3.3 基于System.Net.NetworkInformation.Ping的异步实现除了Unity自带的PingC#的System.Net.NetworkInformation命名空间里也有一个Ping类支持异步方法使用起来更接近现代C#风格。为避免和Unity的Ping冲突一般给这个类起个别名using System.Net.NetworkInformation; using System.Threading.Tasks; using UnityEngine; using DotNetPing System.Net.NetworkInformation.Ping; public static class AsyncPingChecker { public static async Taskbool PingAsync(string host, int timeoutMs 3000) { try { using (DotNetPing ping new DotNetPing()) { PingReply reply await ping.SendPingAsync(host, timeoutMs); return reply.Status IPStatus.Success; } } catch (System.Exception e) { Debug.LogWarning($Ping异常: {e.Message}); return false; } } }调用时直接awaitprivate async void OnLoginButtonClick() { bool online await AsyncPingChecker.PingAsync(223.5.5.5); if (!online) { ShowToast(网络不可用请检查网络后重试); return; } StartLogin(); }3.4 两种Ping实现的取舍对比项UnityEngine.PingSystem.Net.NetworkInformation.Ping使用方式协程/轮询异步/回调并发能力弱推荐串行支持并发WebGL支持受限不支持异常抛出构造阶段可能抛异常方法内部已捕获代码风格偏Unity传统偏现代C#如果项目还在用协程体系或者目标平台包含手机和PCUnityEngine.Ping足够用。如果代码风格已经升级到async/await而且不需要考虑WebGL那.NET版本的Ping用起来更顺手。注意早期版本的Unity中UnityEngine.Ping同一时间只允许一个实例存在同时创建多个会收到“Ping already in progress”的报错。如果你的代码里有并发ping的需求建议维护一个队列串行执行或者换用.NET版本的Ping。4. 实战选型两种方式怎么组合最合理讲完两种方式各自的实现接下来是重头戏在真实项目里到底怎么用。我的经验是不要二选一而是组合出一个状态机让它们各司其职。4.1 推荐的项目级网络状态机我比较喜欢把网络状态定义成三档public enum NetworkStatus { Unknown, // 未知还没检测 NotReachable, // 完全没网 LocalOnly, // 有网卡但外网不通 Online // 外网可达 }检测流程可以这样设计先用GetInterfaceType()查本机接口如果返回None直接置为NotReachable不再做后面的主动探测。如果接口存在再用Ping探测目标主机。Ping通就是Online超时没通就是LocalOnly。这样既能快速处理“飞行模式”这种一眼就知道没网的情况又不会漏掉“路由器没拨号”这种隐蔽故障。伪代码如下public static NetworkStatus DetectNetwork(string probeHost 223.5.5.5) { NetInterfaceType interfaceType NetworkStatusUtil.GetInterfaceType(); if (interfaceType NetInterfaceType.None) { return NetworkStatus.NotReachable; } bool internetReachable AsyncPingChecker.PingAsync(probeHost).GetAwaiter().GetResult(); return internetReachable ? NetworkStatus.Online : NetworkStatus.LocalOnly; }这里要单独提醒一句Unity主线程里用GetResult()同步阻塞异步方法有卡死风险上面的伪代码只是为了表达流程真实项目中建议包到协程或async方法里一步步await别图省事直接用阻塞写法。4.2 探测目标的选取原则Ping的目标主机选择很讲究我踩过几次坑之后总结出了几条原则优先选大厂公共DNS比如223.5.5.5阿里、119.29.29.29腾讯这类IP稳定性好不会轻易挂。不要只配一个目标至少要两个一个挂了自动换另一个。很多玩家使用的是小区宽带运营商链路本身的差异很大一个IP没法代表“全网通不通”。如果能拿到自己服务器的IP而且服务器允许Ping那优先ping自己的服务器因为“玩家能连到你的服务器”才是业务上真正关心的连通性。目标主机不要频繁更换不然缓存下来的历史状态没有对比价值。4.3 不同游戏类型的落地建议单机游戏只在启动画面做个离线提示用internetReachability就完全足够成本最低也不会给玩家带来额外等待。需要登录才能玩的手游和网游登录前必须做一次真正的连通性探测。我建议登录按钮点击后先走一层PingPing通再发登录请求否则用户点完登录等半天超时体验极差。这里要记得在UI上给出明确的“网络检测中”状态不然玩家会以为按钮坏了。有断线重连机制的实时对战游戏光做启动检测不够运行期间也要持续维护网络状态。这时候建议做一个NetworkMonitor组件用低频率轮询我一般2到5秒一次持续检测并且只在状态发生变化时对外发事件避免频繁通知UI刷新。using System; using System.Collections; using UnityEngine; public class NetworkMonitor : MonoBehaviour { public static event Actionbool OnNetworkStateChanged; [SerializeField] private float checkInterval 2f; private bool _lastOnline; private void Start() { _lastOnline CheckNow(); StartCoroutine(CheckLoop()); } private IEnumerator CheckLoop() { while (true) { yield return new WaitForSeconds(checkInterval); bool online CheckNow(); if (online ! _lastOnline) { _lastOnline online; OnNetworkStateChanged?.Invoke(online); } } } private bool CheckNow() { // 这里可以串一个完整的三档检测流程 return NetworkStatusUtil.HasNetworkInterface() AsyncPingChecker.PingAsync(223.5.5.5).GetAwaiter().GetResult(); } }4.4 WebGL等特殊平台的替代思路WebGL平台比较特殊传统ICMP Ping在浏览器沙箱环境下基本不可用UnityEngine.Ping的行为也不稳定。遇到这种情况我通常改用UnityWebRequest发一个轻量的HTTP探测。using UnityEngine; using UnityEngine.Networking; using System.Collections; public class HttpProbe : MonoBehaviour { public IEnumerator CheckServer(string url) { using (UnityWebRequest request UnityWebRequest.Head(url)) { request.timeout 3; yield return request.SendWebRequest(); bool success request.result UnityWebRequest.Result.Success; Debug.Log(success ? HTTP探测成功 : HTTP探测失败); } } }注意WebGL下跨域请求受CORS限制。如果服务器没配好跨域头就算网络通的探测请求也会报错。这种情况不算网络故障但用HTTP探测时容易误判成“不通”最好在逻辑里单独区分CORS错误和其他网络错误。5. 常见问题与排查技巧实录最后把做网络检测时踩过的坑集中列一下很多是文档里查不到的细节。5.1 明明连着Wi-Fi却提示无网络遇到这种情况先别急着怀疑代码。先确认是不是路由器WAN口没拨号成功直接拿手机浏览器试一下能不能上网。如果设备能上网但internetReachability返回NotReachable多半是系统网络状态缓存异常重启Unity编辑器或设备通常能解决。如果是模拟器那先换真机测模拟器的网络枚举行为跟真机差异很大。5.2 Ping超时但网络正常的情况这大概率不是网络问题而是目标主机禁止了ICMP。很多服务器出于安全考虑会屏蔽Ping请求但HTTP服务是正常的。遇到这种情况要么换一个允许Ping的探测目标要么改用HTTP探测。我一般会在项目里配置多个探测目标Ping不通就换下一个全部超时才判定为网络不可用。另外DNS解析失败也会导致Ping超时所以用IP作为探测目标能减少一层干扰。5.3 多平台打包后的行为差异同一个检测逻辑在iOS、Android、PC、WebGL上的表现可能完全不一样。原因主要有三个一是系统网络接口获取方式不同二是ICMP受限程度不同三是浏览器沙箱限制不同。强烈建议在目标平台的真机上各跑一遍检测用例别只看Unity编辑器的结果。编辑器里网络环境比较特殊internetReachability基本默认返回ReachableViaLocalAreaNetworkPing也基本都能通很多问题在编辑器里根本复现不出来。5.4 性能与频率轮询检测的正确姿势不要在Update里每帧Ping也不要在Update里每帧检查internetReachability。虽然internetReachability本身开销极小但Ping是会产生实际网络流量的。我一般这样设计启动时立即检测一次然后每2秒检测一次连续3次全是离线状态才对外报告离线连续2次成功才对外报告在线中间状态不要反复弹窗。这样既不会频繁刷新UI也避免了单次抖动造成的误判。再补充一个实现细节UnityEngine.Ping失败后如果想立刻重试需要重新new一个实例不能复用旧对象。用完之后把引用置空避免不必要的对象长期占用。如果项目里同时有几处地方都要做网络判断最好通过单例或静态工具类统一走同一个检测通道避免多个模块各查各的导致状态不一致。我在实际项目里最深刻的体会是网络检测功能不是“有没有”的问题而是“够不够稳”的问题。很多同学写几行internetReachability判断就交差了但上线之后断线重连、登录超时、资源下载失败全是在这一层栽跟头。如果你做的是需要联网的项目建议从一开始就把“本机接口检测主动探测”这个组合做进去并且预留好缓存和状态事件后面加功能会轻松很多。最后再分享一个小技巧探测目标一定不要只配一个至少配两个IP轮流用一个挂了还能自动切换这个细节在线上环境能少挨好几次骂。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表