System Configuration
0x00 前言
这篇文章是自己在学习 CFHipsterRef Chapter 11 System Configuration 时做的笔记。
0x01 Sysem Configuration
系统配置(Sysem Configuration)包含用于确定硬件配置和网络状态的 C API。
大多数开发者对框架的曝光是通过 Apple 的示例代码,简称 “Reachability”。旨在演示使用系统配置调用来确定互联网连接,如今却成为成千上万的应用程序的一部分。因此,表明 Reachability 是其自身成功的一个受害者。而不是记录系统配置 API,它无意地使它们废弃。
不幸的后果是,大多数开发者对 reachability 是什么,以及如果使用它一知半解。因此,很多那些在其源码中有 Reachability.m
的应用程序可能实际上比根本没有做任何事情的用户体验更糟。
0x02 Reachability
“我连接到互联网了吗?” 是一个难以回答的问题。
从用户的角度来看,应该很容易,对吧?只需要在 Safari 中输入 apple.com
,看看是否有东西加载出来。
0x0201 Determining Network Reachability Synchronously
与任何网络一样,建立 reachability 不应同步进行。然而,为了建立到更复杂的同步使用,看起来像是这样:
1 | @import SystemConfiguration; |
SCNetworkReachability
是负责确定网络可达性(Reachability)的数据类型。它可以通过传递主机名(host name)(如上面示例)或一个 sockaddr
地址来创建:
1 | BOOL ignoreAdHocWiFi = NO; |
SCNetworkReachabilityFlags
是一个同步调用,用于确定可用网络接口的可达性。因为有很多不同的因素影响可达性,所以这个函数返回值不是一个简单的 YES
/ NO
,而是一个特征位掩码(bitmask of characteristics):
Reachability Flag Values
Reachable | 使用当前的网络配置可以访问指定的节点名称或者地址。 |
Transient Connection | 可以通过一个瞬态连接(如 PPP)访问指定的节点名称或地址。 |
Connection Required | 使用当前的网络配置可以访问指定的节点名称或者地址,但必须先建立连接。 |
Connection On Traffic | 使用当前的网络配置可以访问指定的节点名称或者地址,但必须先建立连接。任何针对指定名称或地址的传输都将启动连接。 |
Connection On Demand | 使用当前的网络配置可以访问指定的节点名称或者地址,但必须先建立连接。 |
Intervention Required | 使用当前的网络配置可以访问指定的节点名称或者地址,但必须先建立连接。另外,将需要某种形式的用户干预来建立此连接,例如提供密码,认证 token 等。 |
Is Local Address | 指定的节点名称或地址是与当前系统上的网络接口相关联的。 |
Is Direct | 网络传输到指定的节点名称或地址将不会通过网关(gateway),而是直接路由(routed)到系统的一个接口上。 |
尽管网络接口的复杂性非常是有趣的,但它们只是对 app 开发者的学术练习(academic exercise),就像它们的用户,真的喜欢 YES
或 NO
答案。
下面是如何归纳一个复杂的真理:
1 | BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); |
再进一步,reachability flags 也可以用于确定正在使用哪个网络接口:
1 | if (isNetworkReachable == NO) { |
在主线程上调用 SCNetworkReachabilityFlags
发起一个 30 秒超时的 DNS 查找。这样不好,不要阻塞,同步调用 SCNetworkReachabilityFlags
。
0x0202 Determining Network Reachability Asynchronously
幸运的是,System Configuration 框架提供了一组用于异步监控 reachability 变化的 API。
首先,定义一个静态回调函数,该函数需要网络 reachability reference,flags 以及要传递的任何附加 context。
1 | static void ReachabilityCallback(SCNetworkReachabilityRef target, |
一旦声明了回调函数,它就可以被设置到 SCNetworkReachabilityRef
上,然后在 runloop 上调度:
1 | SCNetworkReachabilityContext context = {0, NULL, NULL, NULL, NULL}; |
现在,每当设置的网络 reachability 发生变化,ReachabilityCallback
函数就会被调用。然后,可以通过发布到 NSNotificationQueue
,或通过调用一个 block 传递到 SCNetworkReachabilityContext
参数中,将此信息传达给应用程序。
0x03 Network Configuration Settings
原谅有人认为 System Configuration 是一个 one-trick pony。几乎每个对该框架的提及都是在引用网络 reachability,也就是它本身是一个以该死的 Reachability 例子为主导的话题。
更模糊的功能与查询系统的动态存储有关:SCDynamicStore
。这些 API 仅在 OS X 上可用。
0x0301 Query
SCDynamicStoreRef
为当前系统配置提供了一个键-值接口,该接口由 configd
守护程序(daemon)管理。
可用使用 SCDynamicStoreCopyKeyList
获取可用的 key 列表:
1 | SCDynamicStoreContext context = { 0, NULL, NULL, NULL, NULL }; |
- Setup:/ Network/Service/…IPv4,
- Setup:/ Network/Service/…IPv6,
- … 100+ Items …
- State:/Network/Interface/p2p0/Link,
- State:/Network/Interface/lo0/IPv6,
- State:/IOKit/LowBatteryWarning,
- State:/Network/MulticastDNS,
- State:/Network/Global/Proxies,
- State:/Network/Interface/bridge0/Link
注:完整的 key 列表可以查看 Apple 的 System Configuration Programming Guide。
有了 key 的列表,SCDynamicStore
的目的变得更清晰。IP 地址和网络接口更改时应用程序能够收到通知,甚至当设备电池过低时也能收到通知。
这里与用于监控网络可达性的语义相同。
例如,可以使用 State:/Network/Interface
key 找到可用网络接口的列表:
1 | SCDynamicStoreContext context = { 0, NULL, NULL, NULL, NULL }; |
- lo0
- gif0
- stf0
- en0
- en1
- en2
- bridge0
- p2p0
- utun0
0x0302 Monitoring
动态存储的真正用处是能够监控更改,以便应用程序可以立即响应如 AirPort 被开启或本地 IP 地址发生变化等事情。
在某种程度上,这只是一个监控网络可达性的更广泛的规划(formulation)。创建一个动态存储,监听一个特定的网络服务实体 key,并且设置一个回调函数指针:
1 | static void Callback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info) { |
语言是我们特有的意识的本质。这就是我们如何思考。这就是为什么我们完全可以思考。
我们依据名词来理解世界,用动词浏览它。形容词将将离散观察结果集中到感觉和判断中。我们通过前辈们的书面记录作为一个社会进步。最后,这将会是我们自己的文字,比我们更久。
我们能够交流完全是一个奇迹。这些想法可以心灵(mind)转移到另一个心灵,是超越信仰的 —— 但是很好理解,它几乎没有记录第二个想法。通过沟通,我们创造彼此之间的理解。我们唤起同情,并扩大我们的道德考虑边界给别人。我们的世界变得更大。
然而,当语言的距离太难以克服,沟通失败时,它变得难以同情。一种别样的感觉。无论是个人的还是国际的,沟通失败仍然是冲突的主要原因。
作为一项消除语言和文化距离的技术,有一个强有力的案例表明,Unicode 是为我们的物种创造的最重要的技术之一。