6.7 KiB
6.7 KiB
内网发现:探测 IP 可配置化 + 移除组播
日期:2026-07-06
状态:已批准(方案 B)
前置:2026-07-06-multicast-discovery-design.md(组播方案,实测跨网段不通,本 spec 将其代码移除)
背景与问题
2026-07-06 真机实测:手机(172.30.37.x)→ 开发机(192.168.130.40)跨网段,组播 1.5s 无回复——路由器未开组播路由(IGMP/PIM),组播与广播一样出不了网段。跨网段唯一可靠路径是单播已知 IP,但该 IP 目前写死在代码里(DevExtraProbeIps),换机器/换网段要改代码重打包。
目标
- 删除全部组播代码(实测无效,不留死代码)。
- 探测 IP 改为设备本地配置:
persistentDataPath/dev_server.txt。 - 底包发现失败且需要目标时,真机上弹输入框让开发者直接输入开发机 IP,探测成功自动写入配置,之后重启不再询问。
- 删除写死的
192.168.130.40,完全由配置文件接替。
非目标
- 不做通用设置界面;输入框仅服务于内网发现这一场景。
- 不支持多 IP / IP:端口(端口固定 48923);配置文件只存一行 IPv4。
- 热更层不弹框(底包阶段已处理,热更层只复用配置)。
- 不改发现协议(magic/端口/编解码不变)。
行为规格
底包启动流程(LoadDll.CoLocalDiscoverThenDownload,仅 bLocal 内网模式)
第1轮 Discover(1500ms, targets=广播+loopback+定向广播+文件IP(若有)) ─成功→ 用发现的 CDN 根,照旧 DownLoadAssets
│失败
↓
挂 DevServerPrompt(输入框预填 DevServerConfig.Load() 旧值,可为空) ←──────────┐
│[确定](输入 IP) │
↓ │
IP 格式非法 → 弹框内红字"IP 格式不对",留在弹框 ─────────────────────────────┤
第2轮 Discover(1500ms, 含输入IP) ─失败→ 弹框内红字"探测失败",留在弹框可改可重试┘
│成功
↓
DevServerConfig.Save(输入IP) → 销毁弹框 → 照旧 DownLoadAssets
任意时刻 [跳过] → UpdateServer 回退 http://127.0.0.1:15081/...(现状行为)
- 文件有记录且第 1 轮失败(开发机换 IP 等)→ 同样弹框,预填旧值(用户确认的行为)。
- 弹框期间加载流程等待,不超时。
- 探测成功但成功来源无法区分(广播/文件 IP),第 1 轮成功不写文件——只有弹框输入并验证成功才写。
热更层(DevServerDiscovery,GameLauncher/DevServerSwitchUI 用)
- 构造探测目标时读
DevServerConfig.Load(),非空则加入 targets。不弹框。调用方零改动。
组件设计
1. Client\Assets\Base\DevServerConfig.cs(新,静态类,全局命名空间)
static string FilePath→Application.persistentDataPath + "/dev_server.txt"static string Load():读首行 → Trim →IPAddress.TryParse通过才返回,否则 null;文件不存在/IO 异常 → null(静默)static void Save(string ip):整文件写入单行;IO 异常静默吞- 仅底包使用。核实发现
XWorld.Link.asmdef引用列表里没有 Base 程序集,热更层引用不到;为 dev 工具改 asmdef 引用不值当,热更层按本项目"内联+两边同步"惯例(协议编解码已如此)在DevServerDiscovery内联同语义的最小读取实现 - 主线程限制:
Application.persistentDataPath不能在后台线程访问,Load()/Save()只能在主线程调;发现线程需要的值由调用方在主线程读好传入/存字段
2. Client\Assets\Base\DevServerPrompt.cs(新,MonoBehaviour,全局命名空间)
- 纯 IMGUI(OnGUI),风格参照热更层
DevServerSwitchUI的 box/横排布局 - 字段:
string InputIp(初始 = 预填值)、State state(Waiting | Confirmed | Skipped)、string Error(非空则红字显示) - OnGUI:提示语("内网发现失败,请输入开发机 IP")+ TextField + [确定] [跳过]
- [确定] →
state = Confirmed(探测由外部做,失败后外部把Error置上并把 state 拨回 Waiting) - [跳过] →
state = Skipped - 不做网络、不做文件 IO——只是输入框
3. Client\Assets\Base\LocalCdnDiscovery.cs(改)
- 删:组播常量/TTL/逐网卡发送/
GetLocalIPv4Addresses/相关注释、DevExtraProbeIps整个字段 - 改:
Discover(string token, int durationMs)→Discover(string token, int durationMs, string extraProbeIp = null);extraProbeIp能 TryParse 则加入 targets - 调用方(LoadDll)自行决定传文件值还是弹框输入值
4. Client\Assets\Script\xmain\MiniGame\DevServerDiscovery.cs(改)
- 删:同上组播相关、
DevExtraProbeIps - 加:内联
ReadConfiguredProbeIp()(读 dev_server.txt 首行+IPv4 校验,与DevServerConfig.Load()语义一致,见组件 1 的说明);构造函数(主线程)里读取存入字段,Run()(后台线程)构造 targets 时使用
5. Client\Assets\Base\LoadDll.cs(改)
CoLocalDiscoverThenDownload按上面流程图重写:两轮发现 + 弹框等待循环;弹框对象gameObject.AddComponent<DevServerPrompt>(),结束后Destroy- 第 1 轮把
DevServerConfig.Load()传入Discover的extraProbeIp
6. 组播代码删除清单
Client\Assets\Framework\Shared\Protocol\DevDiscovery.cs:删MulticastGroup常量及注释Server\Gateway\DevDiscoveryResponder.cs:删JoinMulticastOnAllNics方法、构造函数调用行、using System.Net.NetworkInformation;Server\Gateway.Tests\DevDiscoveryResponderTests.cs:删MulticastQuery_ReceivesReply- 两份客户端:删 TTL 设置、组播端点/网卡列表构建、逐网卡发送循环、零网卡兜底、
GetLocalIPv4Addresses,注释还原为"单播兜底"语义(指向配置文件而非写死 IP)
错误处理
- 文件 IO、IP 解析:全部静默容错,失败等同"无配置"
- 弹框输入非法 IP:[确定] 直接在弹框显示"IP 格式不对",不发起探测
- 探测线程异常:沿用现有 catch-all,视为失败回到弹框
测试与验收
dotnet test Server/Server.sln全量通过(删组播测试后)- 编辑器同机:loopback 一轮即中,弹框不出现,行为与改前一致
- 真机跨网段首启:第 1 轮失败 → 弹框 → 输入 192.168.130.40 → 探测成功 → 写文件 → 正常加载
- 真机重启:文件已有记录,第 1 轮直接命中,不弹框
- 开发机换 IP:第 1 轮失败 → 弹框预填旧值 → 改新 IP → 成功覆写
- [跳过]:回退 127.0.0.1,与现状一致
风险
- IMGUI TextField 在个别 Android 输入法上体验一般(dev 工具可接受;Unity 移动端 IMGUI 会自动唤起软键盘)
- 底包改动须重打 APK 才能上真机(部署铁律,验收时注意)