背景
之所以会学习到这方面的知识,是因为那段时间正在帮一个游戏工作室开发一个游戏自动登录并创建角色的游戏脚本。当时,我就是使用VS去开发。因为它要求要有一个控制端,所以,就分别写了一个客户端程序和控制端程序。客户端都运行在虚拟机内,和控制端在同一网段里。
当时,我就想让客户端在虚拟机里运行,主动去扫描工作组内的主机,那么它工作组内就会有两个主机,一个是虚拟机自己,另一个就是外面的主机。主机上运行则控制端,所以,这样就可以获取主机的IP地址,并自动建立反向连接,传输数据。
函数介绍
// 启动网络资源或现有连接的枚举
// 如果函数成功,返回值为NO_ERROR。
DWORD WNetOpenEnum(
_In_ DWORD dwScope, // 枚举范围
_In_ DWORD dwType, // 要枚举的资源类型
_In_ DWORD dwUsage, // 要枚举的资源使用类型
_In_ LPNETRESOURCE lpNetResource, // 指向指定要枚举的容器的NETRESOURCE结构
_Out_ LPHANDLE lphEnum // 指向可以在随后调用WNetEnumResource中使用的枚举句柄的指针
);
// 继续列出通过调用 WNetOpenEnum 函数启动的网络资源
// 执行成功,返回 NO_ERROR 或者 ERROR_NO_MORE_ITEMS;
DWORD WNetEnumResource(
_In_ HANDLE hEnum, // 标识枚举实例的句柄。由WNetOpenEnum
_Inout_ LPDWORD lpcCount, // 指向指定所请求条目数的变量的指针
_Out_ LPVOID lpBuffer, // 指向接收枚举结果的缓冲区的指针
_Inout_ LPDWORD lpBufferSize // lpBuffer 缓冲区大小
);
// 从主机数据库中检索与主机名对应的主机信息
// 如果没有发生错误,gethostbyname返回一个指向上述主机结构的指针。 否则,它返回一个空指针
struct hostent* FAR gethostbyname(
_In_ const char *name // 指向要解析的主机的以NULL结尾的名称的指针
);
// 将(Ipv4)Internet网络地址转换为Internet标准点分十进制格式的ASCII字符串
// 如果没有发生错误,则inet_ntoa返回一个字符指针,否则返回NULL
char* FAR inet_ntoa(
_In_ struct in_addr in // 一个表示Internet主机地址的in_addr结构
);
实现原理
本文要实现的功能就是遍历网络邻居,获取工作组内的所有在线主机名以及根据主机名获取的IP地址。实现过程如下:
- 首先,我们通过 WSAStartup 函数完成对 Winsock 服务的初始化,因为下面我们会使用到 Socket 函数。
- 然后,我们使用 WNetOpenEnum 设置枚举的范围为本工作组内 RESOURCE_CONTEXT,并获取枚举句柄。
- 接着,我们便调用 WNetEnumResource 函数按照设置的范围去枚举资源,并获取枚举结果。
- 然后,遍历枚举结果,并设置过滤标志 RESOURCEUSAGE_CONTAINER 和 RESOURCETYPE_ANY。通过过滤之后,可以根据远程主机名调用函数gethostbyname 去获取IP地址信息,并通过 inet_ntoa 函数将(Ipv4) Internet网络地址转换为Internet标准点分十进制格式的ASCII字符串。
编码实现
#include <Winnetwk.h>
#pragma comment(lib, "Mpr.lib")
#pragma comment(lib, "Ws2_32.lib")
// 枚举工作组内的网络资源
BOOL EnumNetResource()
{
NETRESOURCE *NetResource = NULL;
HANDLE hEnum;
unsigned int i;
char szHostName[MAX_PATH] = { 0 };
hostent *host = NULL;
char *lpszIP = NULL;
// 通过WSAStartup函数完成对Winsock服务的初始化
WSADATA wsaData = { 0 };
::WSAStartup(MAKEWORD(2, 2), &wsaData);
// 指定枚举范围, 获取枚举句柄
::WNetOpenEnum(RESOURCE_CONTEXT, NULL, NULL, NULL, &hEnum);
if (hEnum)
{
DWORD Count = 0xFFFFFFFF;
DWORD BufferSize = 2048;
BYTE *pBuffer = new BYTE[2048];
// 根据设置的枚举返回, 获取枚举信息
::WNetEnumResource(hEnum, &Count, pBuffer, &BufferSize);
NetResource = (NETRESOURCE*)pBuffer;
for (i = 0; i < BufferSize / sizeof(NETRESOURCE); i++, NetResource++)
{
// 判断资源类型是否是所有资源 以及 判断资源使用类型是否是容器资源
if (NetResource->dwUsage == RESOURCEUSAGE_CONTAINER &&
NetResource->dwType == RESOURCETYPE_ANY)
{
if (NetResource->lpRemoteName)
{
// 获取远程主机名
::RtlZeroMemory(szHostName, MAX_PATH);
::lstrcpy(szHostName, (char *)((DWORD64)NetResource->lpRemoteName + 2));
// 根据主机名获取IP地址信息
host = ::gethostbyname(szHostName);
if (host == NULL)
{
printf("Error Code:%d\n", ::GetLastError());
continue;
}
// 将(Ipv4)Internet网络地址转换为Internet标准点分十进制格式的ASCII字符串
lpszIP = ::inet_ntoa(*(in_addr *)host->h_addr_list[0]);
// 显示
printf("NetResource->lpRemoteName = %s\n", NetResource->lpRemoteName);
printf("NetResource->lpLocalName = %s\n", NetResource->lpLocalName);
printf("ComputerName = %s\n", szHostName);
printf("ComputerIP = %s\n", lpszIP);
}
}
}
// 释放内存并关闭句柄
delete[]pBuffer;
pBuffer = NULL;
::WNetCloseEnum(hEnum);
}
return TRUE;
}