问题
在一次实验中,我需要程序可以自动检测到网络变化,例如:
1) 某一个或某几个网卡被禁用
2) 某一个或某几个网卡的地址变化
NotifyAddrChange可以帮我做到这些。今天来讲一讲这个函数的用法及程序功能在架构阶段需要注意的地方。
如何使用NotifyAddrChange
NotifyAddrChange函数主要的功能是用来通知应用程序IPv4地址的变化。注意这个函数仅仅支持IPv4地址的变化,如果需要得到IPv6地址的变化,请使用NotifyIpInterfaceChange。但这个函数,不在今天的内容范围。
我们先来看函数原型:
DWORD NotifyAddrChange(
PHANDLE Handle,
LPOVERLAPPED overlapped
);
参数:
Handle: 这里需要提供一个指向HANDLE的指针,当函数成功执行后,此指针将指向一个文件句柄,我们可以使用这个句柄来调用GetOverlappedResult来获取重叠I/O操作的详细信息。
overlapped:一个指向OVERLAPPED结构体的指针,此结构体主要包含了重叠I/O的信息。
从以上两个参数可以看出,NotifyAddrChange这个函数支持异步操作,也即重叠I/O操作。
重叠I/O
在Win32 API层面,某些涉及I/O的函数的执行模式可以是同步的,也可以是异步的,比如WriteFile,ReadFile, DeviceIoControl。
当一个函数以同步的方式执行时,它会一直等待I/O操作执行完成,也即,调用这个函数的线程会阻塞。
当一个函数以重叠I/O的方式执行时,这个函数会立即返回,不管I/O操作是否完成。这个执行模式可以使得较为耗时的I/O操作在后台执行,调用线程可以继续执行其他任务。
那么,如何知道I/O操作是否执行完成呢?
有以下两种方法
1) 通过使用GetOverlappedResult来等待I/O操作是否完成。GetOverlappedResult的升级版GetOverlappedResultEx还可以指定一个等待超时时间。在这种情况下,我们需要将 GetOverlappedResult的最后一个bWait参数设置成TRUE。
2) 在初始化OVERLAPPED结构体时,我们指定一个手动重置的事件对象句柄,然后执行I/O函数调用,此时I/O函数因为执行重叠I/O会立即返回,如果想知道操作什么时候完成,可以使用WaitForSingleObject来等待这个事件句柄,等待成功后,继续使用GetOverlappedResult来获取I/O操作的更详细的操作结果,例如,本次I/O操作实际传输的多少个字节。
如果需要取消重叠I/O,我们可以使用CancelIoEx并传递一个OVERLAPPED结构体,此结构体包含待取消的目标句柄,此函数将尝试取消正在进行的I/O操作。
返回值
NotifyAddrChange的返回值分以下两种情况
1) 同步模式下返回
当我们给Handle和overlapped这两个参数都设置为NULL时,这个函数将以同步的方式执行。如果函数执行成功,则返回NO_ERROR,否则返回以下错误代码:
ERROR_CANCELLED
ERROR_INVALID_PARAMETER
ERROR_NOT_ENOUGH_MEMORY
ERROR_NOT_SUPPORTED
2) 异步模式下返回
如果我们给Handle和overlapped这两个参数都设置为非NULL时,这个函数将以异步方式执行。如果这个函数执行成功,则返回ERROR_IO_PENDING,否则返回其他错误代码(见上)。
通过以上对参数和返回值的理解,我们应该可以知道,NotifyAddrChange执行同步和异步两种工作模式。
1) 同步模式
在此模式下,也即我们将它的两个参数都设置成NULL。调用函数后,函数将阻塞,直到系统侦测到IP地址发生变化。如果该函数返回后,继续调用它,则它继续阻塞,直到下一个IP地址变化事件发生。
2) 异步模式
如果我们给它传入一个OVERLAPPED结构时,此函数将以异步模式执行。也即上面已经描述过的,NotifyAddrChange调用将立即完成并返回WSA_IO_PENDING(执行成功的情况下)。我们可以利用OVERLAPPED这个结构来调用GetOverlappedResult来接收IP地址变化这一事件通知。
如何取消
使用CancelIPChangeNotify这一函数可以取消对IP地址变化的侦测,这种情况一般发生在:
1) 用户显式取消侦测功能
用户可能会通过GUI来表达此需求,此时,NotifyAddrChange已经处于阻塞(同步)或其他工作线程已经在WaitForSingleObject(异步)了,使用CancelIPChangeNotify这一调用,可以立即触发NotifyAddrChange返回(同步)或WaitForSingleObject(异步)返回。
2) 应用程序退出
当整个应用程序退出时,系统会帮我们自动取消,但最好还是由我们显式取消比较好。这样做的好处是可以避免工作线程被强制终止而造成资源泄漏。
侦测到IP地址变化后应该怎么做
NotifyAddrChange只是简单的告诉我们系统的IP地址发生了变化,但是具体是怎样变化,我们无法从此函数调用中得到。这个时候,我们可以使用GetAdaptersAddresses这一函数来得到当前最新的IP地址来判断。
好了,我们来看看具体的示例代码,此代码来自MSDN。

程序架构注意点
对于一个图形界面程序来说,如果将NotifyAddrChange放置到主线程,则会导致主线程阻塞界面卡死。那么我们在程序架构阶段,将NotifyAddrChange安排到一个工作线程中,当系统侦测到IP地址变化,工作线程会得到此通知,进而通过窗口句柄来通知主线程。
示例流程图如下

总结
应用程序自我感知是一个非常有用的用户体验,让我们的用户随时知道系统当前的状态,可以极大的提升用户的好感。
用户会觉得:不错!我不是在跟一个”木头程序”在交互。
本文介绍了如何在Delphi中使用NotifyAddrChange函数来检测网络变化,包括IPv4地址的改变。文章详细讲解了函数的使用方法、重叠I/O、如何取消监听以及在程序设计时应注意的事项,强调了在主线程中使用该函数可能导致界面卡死的问题,并给出了程序架构的建议。
4万+

被折叠的 条评论
为什么被折叠?



