资讯动态

Unity联网检测实战:从internetReachability到Ping的组合判定

发布时间:2026/9/15 16:32:19 来源:尧图企业网站定制
1. 先说说为什么判断联网看似简单做起来却各有各的坑做Unity开发这几年几乎每个涉及网络功能的项目都逃不过一个问题玩家到底连没连上网很多人第一反应是这还不简单Unity不是自带接口吗但真到自己上手写的时候会发现判断联网这件事远没有想象中那么省心。先说个我印象特别深的例子。之前给一个数字孪生项目做客户端启动界面需要根据当前是否联网决定进入在线模式还是离线模式。当时图省事直接判断了Application.internetReachability只要不等于NotReachable就认为是联网状态。结果上线之后收到用户反馈说明明连上Wi-Fi了但一直提示请检查网络。后来一查才发现用户连的是酒店那种需要网页认证的公共Wi-FiUnity那边拿到的网络状态是可达的但实际外网完全不通HTTP请求也全部超时。这个问题当时排查了很久也让我彻底明白了一个道理联网本身其实是一个很模糊的词必须把它拆成两个层面来看。第一层是系统层面的网络状态检测设备有没有网卡连接、是Wi-Fi还是蜂窝网络、系统是否认为当前存在可用网络。这一层Unity自带的API可以直接回答。第二层是业务层面的连通性验证某个具体的服务器域名/IP能不能真正访问通、握手时间多长、是否会被防火墙拦截。这一层必须自己去探测。标题里说的两种方式本质对应的就是这两层。一种是吃Unity现成的状态枚举属于快而不准另一种是主动发起探测请求属于准但慢。实际项目里两者通常需要搭配使用单靠哪一个都会踩坑。这篇文章会把两种方式的原理、代码、边界条件和平台差异全部拆开讲清楚力求你看完能直接抄回自己的项目里用。2. 方式一Application.internetReachabilityUnity自带的状态体检2.1 三个枚举值到底代表什么Application.internetReachability是Unity官方提供的网络状态接口返回一个NetworkReachability枚举取值有三种枚举值含义典型场景NotReachable网络不可达飞行模式、关闭Wi-Fi和移动网络ReachableViaCarrierDataNetwork通过运营商蜂窝网络可达4G/5G移动数据连接ReachableViaLocalAreaNetwork通过局域网可达Wi-Fi、有线以太网连接基本用法很简单几行代码就能拿到状态using UnityEngine; public static class NetworkStateUtil { public static bool IsSystemNetworkAvailable() { return Application.internetReachability ! NetworkReachability.NotReachable; } public static string GetCurrentNetworkDescription() { switch (Application.internetReachability) { case NetworkReachability.ReachableViaCarrierDataNetwork: return 当前使用移动数据网络; case NetworkReachability.ReachableViaLocalAreaNetwork: return 当前使用Wi-Fi/局域网; default: return 当前无可用网络; } } }这段代码可以作为项目里判断系统网络是否就绪的统一入口。想在Update里实时监听网络切换的可以轮询这个接口在返回值发生变化时抛出事件比每次直接访问要省事得多。我习惯在项目里封装一个网络状态管理器所有模块都通过它拿状态避免到处散落Application.internetReachability的调用后续想加缓存、加事件通知也更方便。2.2 官方API的盲区它能告诉你有网但没告诉你能不能通用熟这个API之后你会发现它的定位更像一个系统级体检报告而不是业务级连通测试。它只反映设备在操作系统层面的网络接入情况至于这个网络能不能访问到你的游戏服务器、CDN、登录鉴权域名它完全不关心。讲几个实际踩过的问题第一个是公共Wi-Fi的认证拦截。酒店、机场、商场里的Wi-Fi经常会弹出一个网页要求先登录才能访问外网但在你完成认证之前系统层面网络就是可达状态。这个API会返回ReachableViaLocalAreaNetwork可实际HTTP请求全是超时或者被重定向到认证页面。第二个是路由器断网但Wi-Fi信号正常的场景。光猫出故障、宽带欠费、上游交换机掉线这些情况下设备的Wi-Fi连接还在系统仍然认为网络可达但真实外网已经是断的。对纯单机可选联网功能的产品来说影响不大但如果你项目有强制在线验证、版本强更、登录鉴权这些硬依赖就可能出现UI显示网络正常但所有功能都用不了的诡异状态。第三个是代理和防火墙环境。内网测试机、公司网络里常有代理服务器系统层面网络可达但Unity发起的直连请求可能被代理策略挡掉。internetReachability对这类场景毫无感知。所以我的结论很明确这个API适合做前置判断不适合做最终判定。它最大的价值在于快速过滤“设备根本没连接任何网络”的情况让应用能立刻给出响应但要决定能不能进入依赖服务器通信的业务流程必须等真正探测到目标服务器才行。这也是方式二存在的根本理由。3. 方式二主动Ping目标主机回答外网通不通这个终极问题3.1 为什么我推荐Ping而不是直接发HTTP请求既然internetReachability不够用很多人的第一反应是那直接发起一个UnityWebRequest去请求服务器不就行了请求成功就代表联网失败就代表断网。这个思路方向没错但实际写起来有几个麻烦点。一是HTTP请求太重。合包、鉴权、超时重试、证书校验、跨域策略这些链路任何一个环节出问题都会影响判断结果。尤其是有时候服务器接口本身在报错5xx、4xx但网络其实是通的你很难把服务端返回错误和网络不通区分清楚还得额外解析状态码。二是对服务器压力不友好。如果客户端每隔几秒就发一次完整HTTP请求用来做心跳检测用户量大起来之后对服务器的无谓负载非常可观。而Ping包很小网络层协议也更轻量作为连通性探测的性价比高得多。三是判断链路更短。Ping走的是ICMP或UDP协议直击网络层的连通性不涉及HTTP层应用的响应。只要目标主机能回包就说明中间链路是通的。这个信息对我到底能不能访问到服务器有直接参考价值。当然Ping也有它的边界。很多云服务器默认禁ping或者安全组限制ICMP协议访问这时候Ping不通不代表网络不通。所以实际项目里我不会把Ping作为唯一依据而是把它和HTTP探测、internetReachability三层结合这个后面详细说。3.2 一个可以直接抄走的Ping检测实现Unity的Ping类原理是往目标主机发一个探测包然后等待回包。它的关键成员有两个isDone表示探测是否完成time表示往返时间毫秒。需要注意的是它没有自带超时机制——如果目标主机不可达isDone可能长时间不置为true所以必须自己实现超时控制。我封装了一个带超时和回调的协程版本可以直接在MonoBehaviour里调用using System; using System.Collections; using UnityEngine; public class PingChecker : MonoBehaviour { [SerializeField] private string targetHost www.baidu.com; [SerializeField] private float timeoutSeconds 2f; private Coroutine _pingCoroutine; public void StartCheck(Actionbool, int onFinished) { if (_pingCoroutine ! null) { StopCoroutine(_pingCoroutine); } _pingCoroutine StartCoroutine(DoPing(targetHost, timeoutSeconds, onFinished)); } private IEnumerator DoPing(string host, float timeout, Actionbool, int callback) { // 如果系统网络都不可达直接跳过Ping省一次等待 if (Application.internetReachability NetworkReachability.NotReachable) { callback?.Invoke(false, -1); yield break; } Ping ping new Ping(host); float elapsed 0f; while (!ping.isDone elapsed timeout) { elapsed Time.deltaTime; yield return null; } if (ping.isDone) { int rtt ping.time; ping.DestroyPing(); callback?.Invoke(true, rtt); } else { ping.DestroyPing(); callback?.Invoke(false, -1); } _pingCoroutine null; } }调用方式PingChecker checker gameObject.AddComponentPingChecker(); checker.StartCheck((success, rtt) { if (success) { Debug.Log($Ping成功往返时间 {rtt}ms); // 进入在线模式 } else { Debug.Log(Ping超时判定为外网不可达); // 进入离线模式或提示重试 } });有几个细节必须提醒Host不要带协议头。写成https://www.baidu.com在部分平台上会导致初始化失败最好直接传纯域名www.baidu.com或IP地址180.101.50.242。同一时间只保留一个Ping实例。频繁创建多个Ping对象在某些Android真机上可能抛异常或导致资源泄漏项目里尽量串行执行或者复用同一个PingChecker组件。超时时间建议控制在1.5秒到3秒之间。太短容易误判弱网太长会让玩家在断网时等得暴躁。我一般默认2秒弱网场景再加一次重试。重试机制要有退避策略。不要每帧都去Ping简单场景可以连续重试2~3次间隔1秒复杂场景建议指数退避比如1秒、2秒、4秒逐次拉长避免在断网状态下疯狂发探测包浪费电量和流量。关于Ping的返回值time不同平台对它的实现略有差异有时候拿到的可能是0。判断连通性的时候主要看isDone是否为truetime只做参考不要把它当作精确的延迟标准。如果要测玩家到服务器的真实延迟还得用业务层协议本身去测Ping的延迟只能作为辅助参考。4. 组合判定策略实际项目中我把两种方式串成了一条判定链路4.1 从有网到能连服务器的四步判定流程单独用internetReachability会误判单独用Ping有可能因为目标服务器禁ICMP而错杀。真正可靠的方案是把两者串成一条判定链路每层有不同的职责层级判定手段作用通过条件第一层Application.internetReachability快速过滤完全没有接入网络的设备返回不等于NotReachable第二层Ping目标业务域名或公网域名验证当前链路是否具备访问外网的能力在超时时间内收到回包第三层HTTP/HTTPS请求业务接口验证应用层服务是否可用返回预期状态码第四层业务鉴权/数据拉取验证登录态和核心功能链路业务逻辑正常返回实际项目中我会把第一层放在网络切换监听的入口处一旦检测到系统网络变为NotReachable直接弹提示但系统网络变为可达时并不会立刻把在线状态亮给用户而是先进入第二层验证。第二层和第三层一般会合并成一个联网初始化流程代码大致这样组织public class OnlineCheckFlow : MonoBehaviour { [SerializeField] private string pingHost www.baidu.com; [SerializeField] private string apiHost https://api.example.com/health; [SerializeField] private float pingTimeout 2f; public void StartOnlineCheck(Actionbool onResult) { StartCoroutine(RunCheckFlow(onResult)); } private IEnumerator RunCheckFlow(Actionbool onResult) { // 第一步系统网络不可达直接判定离线 if (Application.internetReachability NetworkReachability.NotReachable) { Debug.Log(系统网络不可达); onResult?.Invoke(false); yield break; } // 第二步Ping探测公网连通性 Ping ping new Ping(pingHost); float elapsed 0f; while (!ping.isDone elapsed pingTimeout) { elapsed Time.deltaTime; yield return null; } bool pingOk ping.isDone; ping.DestroyPing(); if (!pingOk) { Debug.Log(Ping超时外网不可达); onResult?.Invoke(false); yield break; } // 第三步用轻量业务接口做最终确认 using (UnityWebRequest request UnityWebRequest.Get(apiHost)) { request.timeout 3; yield return request.SendWebRequest(); bool apiOk request.result UnityWebRequest.Result.Success; Debug.Log(apiOk ? 业务接口可达 : $业务接口异常: {request.result}); onResult?.Invoke(apiOk); } } }前两步都通过后第三步请求业务的轻量接口比如登录前的健康检查接口确认服务器应用层服务正常。这一步不是必须的但要上正式环境建议保留。特别是项目用了服务器热更新、CDN资源分发这些链路时光Ping通域名但CDN挂了的情况并不少见多一次应用层探测能显著降低“看起来有网但功能不可用”的尴尬。4.2 网络状态变化监听与业务接入的细节判定流程搭好之后还需要考虑一个时序问题判断联网不是一次性动作而是贯穿整个游戏生命周期的持续状态。玩家在游戏过程中可能从Wi-Fi切到4G可能坐地铁进隧道突然没信号可能从后台切回前台时网络已经恢复。这些场景都要有对应的处理策略。我常用的做法是在启动时跑一次完整的四步判定之后在Update里以较低频率轮询Application.internetReachability。只有当这个值从NotReachable变为非NotReachable时才重新触发一次完整判定其他情况不做额外操作。public class NetworkMonitor : MonoBehaviour { private NetworkReachability _lastReachability; private bool _isOnline; private void Start() { _lastReachability Application.internetReachability; // 启动时执行一次完整判定 StartOnlineCheck(result _isOnline result); } private void Update() { NetworkReachability current Application.internetReachability; if (current _lastReachability) { return; } _lastReachability current; if (current NetworkReachability.NotReachable) { _isOnline false; // 通知UI弹窗提示网络断开 OnNetworkLost?.Invoke(); } else { // 系统网络恢复了再跑一次完整确认 StartOnlineCheck(result { _isOnline result; if (result) { OnNetworkRestored?.Invoke(); } }); } } }这里有两个经验第一不要在每次网络状态变化时立刻弹出全屏错误提示。网络抖动是常态很可能刚提示网络已断开下一秒又恢复了。我的习惯是断开状态持续2~3秒并且二次确认后才弹提示否则玩家体验会非常糟糕。用InvokeRepeating或者其他延迟机制都可以做到。第二把在线状态做成全局可查询的单例状态而不是散落在各个业务界面里各自判断。登录界面需要判断、资源下载器需要判断、心跳模块需要判断如果每个模块各自写一套逻辑断网时会收到重复弹窗和互相矛盾的提示。统一一个NetworkManager所有模块都读取它的IsOnline属性再配合事件回调能省下大量联调时间。5. 跨平台差异和真机环境里的那些隐藏坑5.1 Unity Editor、Android、iOS、WebGL行为对比同一个Application.internetReachability和Ping在不同平台上的表现差异很大这是很多新手最容易踩雷的地方。下面是我在多个项目里验证过的行为差异平台internetReachability表现Ping类表现注意事项Unity Editor跟随宿主机系统网络状态在编辑器里可用但可能受本机防火墙影响编辑器的网络环境和真机差距大不能作为唯一验证手段Android返回Wi-Fi、移动数据或不可达依赖系统网络权限可用但Android 6.0以上需要联网权限部分定制ROM可能拦截ICMP确保在Manifest里声明INTERNET权限和ACCESS_NETWORK_STATE权限iOS返回Wi-Fi、蜂窝网络或不可达行为与系统版本有关可用但部分公共网络可能限制Ping请求iOS对后台网络检测有限制回到前台时需要重新判定WebGL依赖浏览器的网络状态报告行为不一致Ping类在WebGL平台基本不可用或不稳定需要改用HTTP探测或WebSocket连接状态来判断微信小游戏Unity导出的微信小游戏环境网络状态获取受限Ping不可用主要依赖业务HTTP请求的结果来判断其中WebGL平台要注意的特别多。Ping类在WebGL上基本是废的浏览器安全策略不允许网页直接发起裸ICMP/UDP包。WebGL项目想做网络检测最稳妥的方案是用UnityWebRequest去向自己的业务域名发一个HEAD请求或者GET一个极小的探活资源根据返回结果判断。如果项目做了WebGL和原生平台双端适配建议把网络检测封装成带平台分支的接口不同平台走不同逻辑。微信小游戏平台更特殊。Unity导出微信小游戏后底层很多网络接口被小游戏运行时接管了Ping同样不可用internetReachability返回的结果也未必可靠。我在这个平台上基本只用业务HTTP请求探活短超时快速重试不做花哨的多层判断。5.2 真机排查为什么编辑器里正常到了真机上就判断失败这类问题我遇到太多次了说几个最常见的排查点。权限缺失是最常见的原因。Android平台如果不声明ACCESS_NETWORK_STATE权限某些系统版本上Application.internetReachability会一直返回NotReachable即使Wi-Fi明明连着。Unity默认会帮你加上INTERNET权限用于联网但ACCESS_NETWORK_STATE权限有时需要手动处理。检查方式是在Plugins/Android/AndroidManifest.xml里确认这两行是否存在uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE /目标服务器的IP连通性要提前确认。Ping的域名能不能被目标地区DNS正确解析、服务器安全组是否放行ICMP、带宽是否扛得住客户端频繁探测这些在编码前就要确认好。之前有个项目选了一个海外服务器当Ping目标结果国内玩家访问经常丢包Ping判定成功率只有八成导致部分玩家被误判断网。后来换成国内的两个IP做探测目标问题立刻消失。双网卡和多网络通道设备的特殊情况。部分Android平板同时开了Wi-Fi和移动数据或者插了USB共享网络系统层面的网络状态可能显示为某个优先网络的状况。这种情况internetReachability返回的可能和你预期的不一致但ping和HTTP探测通常能给出真实结果更验证了组合判定的必要性。如果遇到编辑器里网络检测一切正常打包到真机上就失败先别改代码按这个顺序排查先看日志里internetReachability返回的是什么再确认Ping能不能收到回包最后用手机浏览器手动访问目标服务器确认服务器本身没有被运营商或者网络策略拦截。大部分问题都能在这三步里定位到根因。6. 写在最后设计网络检测时我的一些个人习惯做了多年的Unity联网项目踩过的坑多了之后我对网络检测模块的设计已经形成了一套固定的习惯分享出来给大家参考。第一不要把联网检测做成一次性函数而是要做成一个持续运行、有状态的管理模块。网络状态的变化往往出现在玩家切后台、切换Wi-Fi、进出电梯这种不可预测的时刻模块化事件驱动才能应付这些场景。哪怕是简单项目也建议至少留一个NetworkManager脚本作为单例把状态和事件统一管起来。第二判断联网时的UI反馈要分层设计。系统网络断掉时界面可以先显示网络连接已断开这类明确提示系统网络正常但服务器探活失败时提示文案就要换成服务器连接异常请稍后重试不能把所有问题都归为请检查网络设置。这两种提示的混合使用能给玩家更准确的指引也让运营少收到很多不清晰的反馈。第三所有网络检测的IP和域名配置都不要写死在代码里。我在项目里通常会把Ping目标、探活HTTP地址、超时时间、重试策略全部放到配置表或服务端下发的配置里方便线上动态调整。因为你永远不知道某个IP什么时候会被运营商屏蔽、某个域名什么时候会迁移写死在代码里就意味着每次调整都要发版。最后有一个很多团队容易忽略的点网络检测结果要支持日志上报。我在项目里通常会记录每次检测的四步链路结果、耗时、RTT等数据打包上传到日志服务器。线上问题排查时这些历史数据往往能直接还原玩家当时的网络环境比让用户反复描述我就是连不上高效得多。加这个功能成本不高长期收益却很大。网络检测看起来是个不起眼的小功能但它处在所有联网逻辑的最前端一旦判断错误后续所有依赖在线状态的系统都会跟着出问题。希望这篇文章能帮你避开我踩过的那些坑也欢迎在实际开发中自己多测试不同网络环境毕竟真机弱网场景才是检验网络模块的唯一标准。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价