« September 2022 | Main | November 2022 »

October 17, 2022

电信宽带 ipv6 折腾记

周末在家里折腾 BT 下载,发现 UPnP 无法工作,便去光猫那里看看是什么情况。

我发现现在电信宽带默认已经是由光猫来拨号,家中的路由器是作为一个二级路由在使用,如果想让 UPnP 或 DMZ 工作起来,需要在光猫里设置。

查看光猫设备上印的默认管理员密码登录上去,没有找到太多设置选项。在网上搜了一下,需要一个超级管理员身份才能看到全部设置菜单。一般的说法是打电话问一下装宽带的师傅,但天色已晚,不想麻烦人。转念一想,有这么多家宽带设备要管理,电信应该用的是统一密码,或是有一些简单手段可以拿到这个密码才对。否则建立一整套管理系统成本实在太高了。

按 google 搜索结果,打开 192.168.1.1/cgi-bin/baseinfoSet.cgi 后,果然密码就写在这个 json 文件中。虽然不是明文,但和明文也差不多了。密码条目中写的数字就是 ascii 的变形。因为普通密码是已知的,很容易就能看出规则:偏小的数字就是 ascii 码,偏大的数字是 ascii 偏移 4 。用一行 lua 代码就能转换为密码的原文。

拿到超级管理员密码进入管理界面后,便可以打开 DMZ 等端口映射功能。但是我发现现在电信已经不再给 ipv4 的公网地址了,即使做了端口映射也没啥用。按网上的说法,可以打电话给电信要求一个公网 IP ,一般都会爽快的给。但一是时间已晚,人工客户不在线;另外按最新的说法,最近两个月电信也很难要到公网 IP 了。想想也是, IPv4 的地址资源枯竭已久,那么还是看看 IPv6 吧。

在光猫的设置菜单里看到已经获取到 Ipv6 的地址。我的二级路由器是小米 AX9000,还算比较新,登录上去看一下,也是支持 IPv6 的,只是默认关闭。改成 IPv6 Native 模式,使用 https://test-ipv6.com/ 测试一下,发现取不到 ipv6 地址;然后改为 NAT6 模式,这次通了。

但感觉在 ipv6 环境下还使用 NAT 真是个笑话,不是说好的每个设备都应该有自己的 ipv6 地址的么?测试结果是外部看到的是小米路由器的 ipv6 地址,而不是我本地设备的。这肯定是 NAT 的缘故了,我的设备都藏在了路由器 NAT 背后的局域网中。

也就是说,光猫目前正确的接入了 ipv6 网络,拿到了一个 ipv6 地址前缀,并正确的给它后面的小米路由器正确的分配了独立的 ipv6 地址。但当小米路由器启用 Native 模式时,无法再向光猫申请一个子前缀。所以无法使用向下级设备分配独立 ip 这个功能(光猫作为路由器只能向每个直连设备分配唯一 ipv6 地址,无法分配前缀)。小米路由器上还有第三种 ipv6 地址的获取方式,就是用静态 ip 自己填。想想不靠谱,如果光猫重新分配地址,我就得手工跟着改,所以没去折腾。

剩下的就两条路:

  1. 把小米路由器改成有线中继模式,这样等于让插在它上面的设备直接连到光猫上,这样光猫就可以直接给它们分配 IPv6 地址了。

  2. 把光猫改成桥接模式,由小米路由器做一级路由,直接拨号上网。如此,小米路由器便可以直接从运营商那里获取到 Ipv6 前缀,可以由它向下面的设备分配 ipv6 地址。

鉴于小米 AX9000 的性能明显要好过电信送的光猫,我先尝试了方案 2 。

因为已经有了超级管理员密码,所以想来不需要联系电信师傅帮我改桥接模式。自己看了一下现有设置,发现已经不是 PPPoE 的拨号上网方式,而是 IPoE ,并没有用户名密码了。在 IPoE 的模式下,不能选择桥接模式。

抱着试一试的想法,我改回 PPPoE ,并选择桥接模式;想着如果不行我再改回去呗。试过之后,发现果然连不上外网,然后改回去让光猫做路由拨号,还是连不上 :( 从设置界面看,光纤是通的,设备正确注册,但是 DHCP 就是取不到地址。

只好用手机在电信网站提了个报修单,关机睡觉。

第二天一大早 10000 号就拨回电话给我了。客服说我家网络是通的,但是拨号密码错误。我心说你们不是给我改成 IPoE 认证了吗?怎么还要密码。况且我昨晚密码认证试过了,就是拨不上去啊。

电信客服告诉我它那边查询到我家的宽带就是 PPPoE 拨号上网模式的,让我再试试。我估计是电信那边远程更新了设置。然后我开机重新一试,果然连上外网了。

测试了一下,台式机终于拥有了自己的 IPv6 公网地址 :)


记录此流水账皆因现在网络环境变化颇快,宽带运营商不断的升级它们的组网方案。在 google 上搜来的信息很快就过期了。留下一篇最新的记录,可供有最近有此需要的人参考。

October 08, 2022

引擎 IO 模块的变化和发展

我们游戏引擎的 IO 模块其实一直在修改。最初的版本到现在有四年多了

一直没有定稿的一部分原因是因为我们给引擎设定了一个比较高的需求:引擎本身也是可以从网络自更新的。而更新引擎本身必然依赖 IO ,包括 IO 模块自身。而我们引擎又基于一个多线程版本的 Lua 框架 ltask ,ltask 本身也是需要依赖 IO 模块启动的。这些后续的设计要晚于最初 IO 模块的设计,反复重构也就是必然了。

一开始我们为 IO 单独分配了一个线程,它负责和文件服务器通讯以及文件的读写。后来因为整合 ltask ,又花了许多功夫把它从独立线程变为 ltask 的一个服务。但这个服务又有点特殊:因为它还需要负责 ltask 自身的加载。所以它前生是一个 native os thread ,中途再转换为 ltask 的独占服务。

最近,我反思整个模块的演化历程或许被引入了不必要的复杂度。重新考虑把所有的 IO 请求都放到一个单独的线程/服务 中是否是过度设计、似乎应该做一些退让,维护很少几个全局状态,似乎能简化很多的设计工作。

这里需要解决的另一个难题是一些第三方库需要注册一个 IO 回调接口,再需要读写文件时调用框架提供的接口加载文件。这些第三方模块都是把文件操作看作是同步调用,而我们已经实现的 IO 模块本质上提供的是异步接口。

如果一个模块本身是 Lua 实现的,那么同步接口和异步接口的差异很小;但如果回调发生在 C side 中时,就很难把异步接口转换为同步的。因为我们无法从 C side 直接 yield 线程。这也是我们前段时间实现 多线程串行运行 Lua 虚拟机 的动机。实际上,这个特性被实现后,我们最终并没有使用它,改为修改 IO 模块的设计。

当我们回顾游戏引擎本身的需求时,会发现,按需同步加载文件往往并不多见。因为那样一定会阻塞程序的运行。即使在 30fps 的游戏中,超过 33ms 的停顿给玩家的体验也很糟糕,而读文件甚至从网络下载文件,33ms 实在是不够用。

以我们这些年的经历来看,按需加载最大的应用场合是在开发期:开发人员不必忍受反复的 loading 时间,可以做到快速启动、快速复现问题,可以提高开发体验;至于游戏体验被不时的 IO 加载打断倒是次要的。

所以,我想我们应该同时提供两种模式,并可以随时切换。

另一个面临的大问题是资源的生命期管理。如果我们把文件对应的资源逐个交于上层,几乎很难作到精细化管理。要么是直到需要时忍受一下加载停顿加载,等暂时不用就立刻释放;要么就是一开始把需要用到的资源全部加载(并伴随一个 loading 过场)。这两个方案上层都不会花太多精力做精细化管理。

所以,我在资源文件之上增加了一个 asset bundle 的概念。这个词是从 Unity 中借来的,别的引擎应该也类似。

bundle 是一系列资源文件的集合。我们可以在开发期给 bundle 中的每个文件标记上针对这个文件的管理策略:可以是必须在初始化时加载、也可以是按需加载;鉴于我们引擎的特性,按需加载也分为从网络下载以及加载到内存。

和 Unity 的具体实现不同,我们的文件是不打包的(或是说不按 bundle 打包),bundle 仅仅是描述引用了哪些文件以及应如何管理这些资源。不同的 bundle 允许引用重复的文件,但管理策略可以不同。

游戏上层逻辑不用精细的去管理每个具体文件,而只需要针对 bundle 进行管理:可以打开或关闭某个特定 bundle 。在引用资源的时候,也不从 bundle 开始索引文件,即,不必去打开具体某个 bundle 中的某个文件;所有 bundle 中的文件总集还是摊在一个文件树上的。所以打开具体资源文件的 api 的参数还是文件路径而不是 bundle + 路径。只不过,如果一个 bundle (在特定服务中)未曾被打开,那么其中包含的文件也会打开失败。

在当前的实现中,我把文件树实现为全局共享,而不再通过服务间消息传递。这样在集成第三方库时要简单的多。估计性能也有所提高。但是每个服务所打开的 bundle 是独立管理的,这样一定程度上还有一些隔离。这可以帮助我们发现资源管理逻辑上的 bug 。


另外,一些特定的资源不在文件层面管理。例如贴图,是基于贴图的 handle 进行管理的。这可以方便我们在贴图缺失时可以先用替代贴图顶替。从业务层面看,贴图永远可以以同步模式加载,即使内部机制是异步加载的(这由 bundle 的参数控制)。

而其他一些资源,如果需要做异步加载,则需要上层逻辑配合异步 IO 的接口实现,而不是把细节藏起。