17

我必须能够检测到我的 Mac 客户端的 IP 地址更改。每次我得到一个新的,当我从wifi转到有线时,我都需要执行一个动作......

有人做过类似的事情吗?我目前每分钟投票一次,我需要将其更改为更多事件驱动。

4

1 回答 1

30

有多种方法可以做到这一点,从 IOKit 通知开始,但最简单的可能是 SystemConfiguration 框架。

第一步是启动 scutil 并使用它来确定您想要通知哪些键:

$ scutil
> list
...
> n.add State:/Network/Global/IPv4
> n.watch
... unplug your network cable (or disconnect from WiFi)
notification callback (store address = 0x10e80e3c0).
changed key [0] = State:/Network/Global/IPv4

看看这个,第一次尝试就知道了。:) 但是,如果您想查看特定的 NIC,或者使用 IPv6 而不是 v4 等,显然您需要列表中的不同密钥。请注意,您可以使用正则表达式模式(POSIX 样式,由 定义man 3 regex),因此如果您想查看 IPv4 的任何 NIC,您可以使用State:/Network/Interface/.*/IPv4,或者如果您想说全局 IPv4 或 IPv6 State:/Network/Global/IPv.,等等。

现在您只需使用您想要的密钥调用 SCDynamicStoreSetNotificationKeys。

请注意,SCDynamicStoreSetNotificationKeys 可以采用正则表达式模式(POSIX 样式,由 man 3 正则表达式定义)

由于在 C 中有点痛苦,我将在 Python 中编写它:

#!/usr/bin/python

from Foundation import *
from SystemConfiguration import *

def callback(store, keys, info):
  for key in keys:
    print key, SCDynamicStoreCopyValue(store, key)

store = SCDynamicStoreCreate(None, 
                             "global-network-watcher",
                             callback,
                             None)
SCDynamicStoreSetNotificationKeys(store,
                                  None,
                                  ['State:/Network/Global/IPv4'])
CFRunLoopAddSource(CFRunLoopGetCurrent(),
                   SCDynamicStoreCreateRunLoopSource(None, store, 0),
                   kCFRunLoopCommonModes)
CFRunLoopRun()

这在 C 中更痛苦的主要原因是,您需要数十行样板代码来创建包含 CFString 的 CFArray、打印 CFString 值、管理对象的生命周期等。根据 Jeremy Friesner 的评论,有C++如果您宁愿阅读 113 行 C++ 而不是 17 行 Python,则可以使用示例代码。但实际上,这里只有一行对于从未使用过 Python 的人来说应该是陌生的:

def callback(store, keys, info):
  for key in keys:
    print key, SCDynamicStoreCopyValue(store, key)

... 相当于 C 定义:

void callback(SCDynamicStoreRef store, CFArrayRef keys, void *info) {
  /* iterate over keys, printing something for each one */
}

奇怪的是,我再也找不到关于 SystemConfiguration 的实际参考或指南文档了;SCDynamicStoreSetNotificationKeys 或相关功能的唯一内容是CFNetwork Programming Guide的 Navigating Firewalls 部分。但是最初的技术说明 TN1145:Living in a Dynamic TCP/IP Environment仍然存在,并且它有足够的背景和示例代码来弄清楚如何自己编写(以及如何在收到通知时检测新的 IP 地址)。

显然,这需要你知道你到底要注意什么。如果你不知道,没有人可以告诉你如何注意它。您最初的问题是如何“检测 IP 地址更改”。

上面的代码将检测您的默认地址何时更改。这是当您将套接字连接到互联网地址而不绑定它时使用的地址,或者将套接字绑定到“0.0.0.0”以充当互联网服务器。如果您没有编写您关心的服务器代码,几乎所有网络客户端都执行前者,而大多数服务器执行后者,除非您另外配置它​​们,所以您可能只关心这些。

现在让我们一一浏览您评论中的示例:

如果我想确定网络变化,wifi 到 LAN

没有从 WiFi 到 LAN 的变化。当您连接到 LAN 时,WiFi 仍在工作。当然,您可以在连接到 LAN 之前或之后手动禁用它,但您不必这样做,这是一个单独的步骤,带有单独的通知。

通常,添加 LAN 会将您的默认地址更改为 LAN 的地址,因此/Network/Global会通知您。如果操作系统可以判断 LAN 没有真正连接到互联网,或者您更改了一些隐藏设置使其更喜欢 WiFi 而不是 LAN 等,它不会更改默认地址,/Network/Global也不会通知您,但你可能不在乎。

如果您确实关心特定接口是否获取、丢失或更改地址,则可以观察该接口。在大多数 Mac 上,内置以太网为 en0,内置 WiFi 为 en1,但当然您可能有第三方 USB WiFi 连接器,或者您可能正在使用系留手机,或者您可能感兴趣与其说是 LAN 的实际 IP 地址,不如说是 LAN 所连接的 VPN 的 VPN 地址等。如果您正在编写一些特殊用途的东西,您可能知道您关心哪个接口,因此您可以观看,例如,State:/Network/Interface/en0/IPv4。如果您想在任何界面发生变化时收到通知,请观看State:/Network/Interface/.*/IPv4

或热点或其他wifi

从一个 WiFi 网络更改为另一个(热点或其他)将更改 en1,或者,如果您使用的是第三方 WiFi 适配器,则会更改其他接口。如果您当时的默认地址来自 WiFi,它也会更改Global,这意味着上面的代码将按原样工作。如果您的默认地址仍然是您的 LAN,Global则不会更改,但您可能不在乎。如果您确实关心,请观看Interface/en1Interface/.*等,如上所述。

对于 IPV4 和 6,我应该注意哪些网络设置

只需将 IPv4 替换为 IPv6,或使用IPv.. 但是你真的关心 IPv6 吗?

还有什么

你还关心什么?如果您确实想要通知某些东西,那么您大概知道那是什么。

除此之外,如果系统告诉您 bar 界面上的 foo 地址已更改为“ZZ9 Plural Z Alpha”,而您从未听说过 foo 协议,那么您可以用这些信息做什么?但是如果你真的想要它,你可以再次使用正则表达式模式来监视每个接口下的任何内容。

于 2012-07-17T23:53:34.463 回答