Windows DHCP 故障转移关系删除实战:DhcpV4FailoverDeleteRelationship API 使用指南(Windows-classic-samples)
Windows DHCP 故障转移关系删除实战:DhcpV4FailoverDeleteRelationship API 使用指南(Windows-classic-samples)
本文以 Windows-classic-samples 仓库中的 DeleteRelationship 示例 为主线,深入讲解 Windows DHCP Server 管理 API 中
DhcpV4FailoverDeleteRelationship的完整调用流程。读者将掌握如何通过代码安全地删除 DHCP 服务器上的故障转移(Failover)关系,包括删除前的作用域停用前置步骤、主备服务器两侧的对称删除、错误容忍策略以及 RPC 内存释放规范,并了解该 API 在 DHCP 故障转移关系生命周期中的定位。
一、DHCP 协议与故障转移关系背景
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是一种标准化协议,它允许客户端被动态分配 IP 地址、子网掩码、默认网关以及其他关键网络配置参数。DHCP 服务器集中管理这类配置数据,由网络管理员根据具体网络环境为其设置适当的参数;随后,DHCP 服务器通过 DHCP 消息与 DHCP 客户端进行通信。
在 DHCP 服务器部署中,故障转移(Failover) 是一项高可用能力:两台 DHCP 服务器(主服务器 Primary Server 与伙伴服务器 Partner/Secondary Server)就一组作用域(Scope)建立故障转移关系,彼此互为备份。当一台服务器不可用时,另一台可以继续为客户端提供服务。
DhcpV4FailoverDeleteRelationship 正是 DHCP Server 管理 API(DHCP Server Management API)中用于删除已建立的故障转移关系的接口。它属于 Windows 8.1 / Windows Server 2012 R2 引入的 DHCPv4 故障转移管理 API 家族,与 DhcpV4FailoverCreateRelationship、DhcpV4FailoverGetRelationship、DhcpV4FailoverSetRelationship、DhcpV4FailoverEnumRelationship 等接口共同构成一套完整的关系管理工具链。
二、示例定位与运行环境
本示例对应仓库中的 DeleteRelationship 示例目录,核心文件包括:
| 文件 | 作用 |
|---|---|
| README.md | 示例说明文档(本文章所依据的主体文档) |
| DeleteRelationship.cpp | 示例源码,完整演示 API 调用流程 |
| DeleteRelationship.vcxproj | Visual Studio 工程文件 |
| DeleteRelationship.sln | 解决方案文件 |
操作系统要求:
- 客户端:Windows 8.1
- 服务器:Windows Server 2012 R2
该示例最初使用 Visual Studio 2013 为 Windows 8.1 / Windows Server 2012 R2 编写,但多数情况下可直接使用后续版本的工具链与操作系统原样编译运行。工程文件采用 PlatformToolset v110、CharacterSet 为 Unicode、子系统为控制台(Console)程序,且同时提供 Debug/Release 两种 x64 配置。
使用前提:示例中的关系名硬编码为 L"test"(见 DeleteRelationship.cpp),运行前需先在 DHCP 服务器上存在名为 "test" 的故障转移关系。可先运行仓库中 CreateRelationship 示例 创建该关系。
三、核心 API:DhcpV4FailoverDeleteRelationship
从 DeleteRelationship.cpp 的调用形式可以看到,该 API 的典型调用方式为:
dwError = DhcpV4FailoverDeleteRelationship(
ServerIPAddress, // DHCP 服务器 IP 地址(LPWSTR)
RelationshipName); // 要删除的故障转移关系名称(LPWSTR)
参数含义(依据源码调用形式与注释整理):
| 参数 | 含义 | 取值说明 |
|---|---|---|
ServerIPAddress |
目标 DHCP 服务器地址 | 传 NULL 表示当前程序所在的本地服务器;传具体服务器名称/IP 表示远程服务器 |
RelationshipName |
待删除的故障转移关系名称 | 与创建关系时使用的名称一致,示例中为 L"test" |
函数返回 DWORD 错误码,ERROR_SUCCESS(0)表示成功;失败时返回对应的 Win32/DHCP 错误码。示例中通过 wprintf 输出错误码,便于调试定位。
该接口依赖 dhcpsapi.h 头文件与 dhcpsapi.lib 导入库,这也是工程在链接选项中显式追加 dhcpsapi.lib 的原因(见 DeleteRelationship.vcxproj)。
四、删除关系的完整执行流程(源码逐段解读)
DhcpV4FailoverDeleteRelationship 不能孤立使用——直接删除仍承载活动作用域的关系可能造成服务中断。因此示例演示了标准的"先停用作用域,再删除关系"安全流程。整体执行顺序如下:
4.1 第一步:获取现有关系
在删除之前,先用 DhcpV4FailoverGetRelationship 查询目标关系,拿到完整的关系结构与两侧服务器信息:
dwError = DhcpV4FailoverGetRelationship(
pwszServer, // 服务器地址,NULL 表示本地服务器
pwszRelationshipName, // 关系名称 "test"
&pRelationShip); // 输出:LPDHCP_FAILOVER_RELATIONSHIP
if( ERROR_SUCCESS != dwError)
{
wprintf(L"DhcpV4FailoverGetRelationship failed with Error = %d\n",dwError);
goto cleanup;
}
如果关系不存在,DhcpV4FailoverGetRelationship 会返回失败并直接跳转到清理段退出,避免后续步骤在无效数据上执行。
4.2 第二步:停用两侧服务器上的作用域(关键前置步骤)
关系结构中包含 pScopes(LPDHCP_IP_ARRAY,关系覆盖的作用域 IP 数组)。示例定义 DeactivateScopes 辅助函数,逐一将关系涉及的作用域置为停用状态:
DWORD DeactivateScopes(_In_ LPWSTR pServer, _Inout_ LPDHCP_IP_ARRAY pArray)
{
for(dwIndex = 0 ; dwIndex < pArray->NumElements ; dwIndex++)
{
// 1. 查询作用域详细信息
dwError = DhcpGetSubnetInfoVQ(pServer, pArray->Elements[dwIndex], &pSubnetInfoVQ);
// 2. 将作用域状态置为停用
pSubnetInfoVQ->SubnetState = DhcpSubnetDisabled;
// 3. 写回服务器
dwError = DhcpSetSubnetInfoVQ(pServer, pArray->Elements[dwIndex], pSubnetInfoVQ);
// 4. 释放查询结果内存
FreeSubnetInfoVQ(pSubnetInfoVQ);
}
}
调用链为:DhcpGetSubnetInfoVQ(查询)→ 修改 SubnetState = DhcpSubnetDisabled → DhcpSetSubnetInfoVQ(写回)。随后示例对两侧服务器分别执行停用:
// 停用伙伴(Secondary)服务器上的作用域
dwError = DeactivateScopes(pRelationShip->SecondaryServerName, pRelationShip->pScopes);
// 停用主(Primary)服务器上的作用域
dwError = DeactivateScopes(pRelationShip->PrimaryServerName, pRelationShip->pScopes);
4.3 第三步:删除伙伴服务器上的关系
在停用作用域后,先在伙伴服务器上删除关系:
dwError = DhcpV4FailoverDeleteRelationship(
pRelationShip->SecondaryServerName, // 伙伴服务器
pwszRelationshipName); // 关系名 "test"
if( ERROR_SUCCESS != dwError)
{
wprintf(L"DhcpV4FailoverDeleteRelationship failed with Error = %d\n",dwError);
// 注意:此处不中止后续流程
}
这里体现了一个重要的错误容忍策略:即使伙伴服务器上的关系删除失败,示例也不会中断执行,而是继续在当前服务器上删除。源码注释说明了原因:如果两台服务器之间的 TCP 连接中断、伙伴服务器暂时不可达,那么不应该因此阻碍在当前服务器上完成删除操作。这种"尽力而为、逐步收敛"的思路对跨服务器管理操作很有参考价值。
4.4 第四步:删除当前服务器上的关系
最后在当前(主)服务器上删除关系:
dwError = DhcpV4FailoverDeleteRelationship(
pwszServer, // NULL 表示本地服务器
pwszRelationshipName);
if( ERROR_SUCCESS != dwError)
{
wprintf(L"DhcpV4FailoverDeleteRelationship failed with Error = %d\n",dwError);
}
4.5 第五步:统一清理内存
流程结束后统一调用 FreeRelationshipMemory(pRelationShip) 释放 DhcpV4FailoverGetRelationship 分配的关系内存。
五、数据结构的理解:DHCP_FAILOVER_RELATIONSHIP
删除操作依赖关系结构携带的服务器名与作用域信息,因此理解 DHCP_FAILOVER_RELATIONSHIP 是正确使用的前提。字段含义可参照 CreateRelationship 示例 的完整初始化代码整理如下:
| 字段 | 含义 | 示例取值(源码中) |
|---|---|---|
PrimaryServer |
主服务器 IP 地址 | 0xa000001(10.0.0.1) |
SecondaryServer |
伙伴(备用)服务器 IP 地址 | 0xa000002(10.0.0.2) |
Mode |
故障转移模式 | LoadBalance(负载均衡)或 HotStandby(热备) |
ServerType |
本端在关系中的角色 | PrimaryServer 或 SecondaryServer |
State / PrevState |
关系当前状态 / 上一状态 | NO_STATE |
Mclt |
最大客户端租约时间(Maximum Client Lead Time),单位秒 | 0xe10(3600 秒,约 1 小时) |
SafePeriod |
安全期,单位秒 | 0x258(600 秒,10 分钟) |
SharedSecret |
两台服务器共享的认证密钥 | L"secretpassword" |
Percentage |
负载均衡百分比(仅 LoadBalance 模式有效) | 0x32(50%) |
PrimaryServerName |
主服务器名称 | L"failover-test1" |
SecondaryServerName |
伙伴服务器名称 | L"failover-test2" |
RelationshipName |
关系名称 | L"test" |
pScopes |
关系覆盖的作用域 IP 数组(LPDHCP_IP_ARRAY) |
单元素数组,元素为 0xa000000(10.0.0.0) |
值得注意:pScopes->Elements 中的 IP 地址采用主机字节序存储(如 0xa000000 即 10.0.0.0),这是 DHCP 管理 API 的常见约定,编码时不要与网络字节序混淆。
六、内存释放规范:DhcpRpcFreeMemory
示例中所有由 DHCP 管理 API 返回的内存(RPC 分配的内存)都必须通过 DhcpRpcFreeMemory 释放,而不能使用普通的 free/HeapFree。这一点在两个辅助函数中体现得很清楚:
FreeSubnetInfoVQ(DeleteRelationship.cpp):依次释放PrimaryHost.NetBiosName、PrimaryHost.HostName、SubnetName、SubnetComment后释放整个DHCP_SUBNET_INFO_VQ结构;FreeRelationshipMemory(DeleteRelationship.cpp):依次释放RelationshipName、SharedSecret、PrimaryServerName、SecondaryServerName、pScopes(含其Elements数组),最后释放整个DHCP_FAILOVER_RELATIONSHIP结构,并将指针置NULL。
这也是为什么示例在 cleanup 段集中释放内存的原因:任何一步失败都会 goto cleanup,确保不泄漏由 RPC 分配的内存。
七、故障转移关系的完整生命周期
DhcpV4FailoverDeleteRelationship 是关系生命周期的收尾环节。结合仓库中同一 Samples/DHCP 目录下的其他示例,可以看到完整的操作闭环:
| 阶段 | API | 对应仓库示例 |
|---|---|---|
| 创建关系 | DhcpV4FailoverCreateRelationship |
CreateRelationship |
| 查询单个关系 | DhcpV4FailoverGetRelationship |
GetRelationship |
| 枚举全部关系 | DhcpV4FailoverEnumRelationship |
EnumRelationship |
| 修改关系参数 | DhcpV4FailoverSetRelationship |
SetRelationship |
| 从关系移除作用域 | DhcpV4FailoverDeleteScopeFromRelationship |
DeleteScopeFromRelationship |
| 删除整个关系 | DhcpV4FailoverDeleteRelationship |
DeleteRelationship |
其中 SetRelationship 示例 展示了用 dwFlags(MODE、SAFEPERIOD、MCLT、PERCENTAGE)按位标记哪些字段需要修改的用法,而 EnumRelationship 示例 展示了基于 ERROR_MORE_DATA / ERROR_NO_MORE_ITEMS 的分页枚举模式——这些都是理解故障转移关系管理 API 家族的良好补充材料。
八、构建与运行示例
构建
- 在 Visual Studio(Express 2013 for Windows 8.1 或任意后续版本)中打开 DeleteRelationship.sln 解决方案文件;
- 按
F7(Visual Studio 2013 为F6),或从顶部菜单选择 Build → Build Solution; - 编译产物默认生成在工程下的 Debug 或 Release 目录中(工程提供 x64 平台的 Debug/Release 两种配置)。
工程构建要点(来自 DeleteRelationship.vcxproj):
- 控制台子系统(
<SubSystem>Console</SubSystem>),输出可直接在命令行运行; - Unicode 字符集(
UNICODE;_UNICODE),配合LPWSTR宽字符串参数使用; - 链接时附加
dhcpsapi.lib,提供 DHCP 管理 API 的导入; - 警告级别 Level4 且
TreatWarningAsError为 true,编译质量门槛较高。
运行
- 在 Visual Studio 中按
F5(启用调试运行)或Ctrl+F5(不调试直接运行),也可从 Debug 菜单选择对应选项; - 运行前需保证目标 DHCP 服务器上已存在名为
test的故障转移关系; - 程序将依次:查询关系 → 停用主备两侧作用域 → 删除伙伴侧关系 → 删除当前侧关系 → 释放内存,过程中任何失败都会通过控制台输出对应的 API 名称与错误码。
九、注意事项与排错建议
- 删除前必须停用关系内作用域:示例的顺序并非随意——先停用两侧作用域(
DhcpSubnetDisabled),再执行删除,这是避免删除过程中产生地址分配冲突的稳妥做法。 - 两侧对称删除:故障转移关系同时存在于主备两台服务器的配置中,删除时需分别在
SecondaryServerName与本地服务器两侧调用,才能彻底移除。 - 容忍单侧失败:伙伴服务器不可达(如 TCP 连接断开)时,删除操作仍应在当前服务器继续,示例以不中断流程的方式演示了这一原则。
- 使用正确的释放函数:凡是 DHCP 管理 API 返回的内存,一律通过
DhcpRpcFreeMemory释放,避免内存泄漏或堆损坏。 - 错误码排查:所有 API 的返回值都是
DWORD错误码,示例统一以wprintf(L"...failed with Error = %d\n", dwError)输出,可据此快速定位失败环节(如关系不存在、服务器不可达、权限不足等)。
十、延伸阅读
仓库中 Samples/DHCP 目录 还包含大量 DHCP Server 管理 API 的配套示例,覆盖策略管理(如 CreatePolicy、SetPolicy)、作用域管理(如 GetScopeStatistics)、客户端信息(如 V4GetClientInfo)等场景,与本示例同属 Windows DHCP 服务器管理能力的完整演示集合,可作为进一步研究的参考。