多网卡情况下如何正确获得IP

     这几天在自己的个人机器上装了个虚拟机VMWare。然后在程序中获得本地IP,发现,是错误的,发现获得的IP竟然不是原来的192.168.1.155,而是一个奇怪的192.168.233.1。后来发现,原来自己装了虚拟机,这个地址是自己虚拟机VMWare VMNet8的地址,而且,这个网络被命名为本地连接2。

     看下我自己原来的代码,是用的gethostbyname方法获得的。于是,写了个Demo程序,进行运行

     

#include
#include
#include
#include

int main(int argc, char *argv[])
{
	char *ptr, **pptr;
	struct hostent *hptr;
	struct hostent *pHPtr;
	char str[32];
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD( 2, 0 );

	char name[100];

	ptr = name;

	if (0 ==  WSAStartup( wVersionRequested, &wsaData ) )
	{

		if(0 != gethostname ( ptr, strlen(ptr)) )
		{
			printf("没有获得名称\n");
			return -1;
		}

		if (NULL == (hptr = gethostbyname(ptr)))
		{
			printf("获得地址错误\n");
			return -1;
		}

		printf("机器的正式名字是: %s\n", ptr);

		for (pptr = hptr->h_aliases; *pptr != NULL; pptr++)
		{
			printf("别名是: %s\n", *pptr);
		}

		int i = 1;

		switch(hptr->h_addrtype)
		{
		case AF_INET:
		case AF_INET6:
			pptr = hptr->h_addr_list;
			
			for(; *pptr != NULL; pptr++)
			{
				printf("地址是: %s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
			}

			pHPtr = hptr;
						
			for (pptr = hptr->h_addr_list; *pptr != NULL; pptr++)
			{
				//inet_ntop的实质
				printf("第%d 个地址是: %d.%d.%d.%d\n", i, pHPtr->h_addr_list[i-1][0] & 0x00ff, pHPtr->h_addr_list[i-1][1] & 0x00ff, 
					pHPtr->h_addr_list[i-1][2] & 0x00ff, pHPtr->h_addr_list[i-1][3]& 0x00ff);
				printf("第%d 个地址是: %s\n", i++, inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)) );
			}

			printf("第一个地址是: %s\n", inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str)));
			break;

		default:
			printf("未知地址类型\n");
		}

		WSACleanup( );
	}	

	return 0;
}

运行结果如图所示:

多网卡情况下如何正确获得IP_第1张图片

原来,第一个地址早已不是我自己的本地地址,而是VMNet8 的地址。

我查下相应函数的用法,相应信息如下:

 struct hostent *gethostbyname(const char *name);
    这个函数的传入值是域名或者主机名,例如"www.google.cn"等等。传出值,是一个hostent的结构。如果函数调用失败,将返回NULL。
    返回hostent结构体类型指针
    struct hostent
    {
        char    *h_name;               
        char    **h_aliases;
        int     h_addrtype;
        int     h_length;
        char    **h_addr_list;
        #define h_addr h_addr_list[0]
    };
    hostent->h_name
    表示的是主机的规范名。例如www.google.com的规范名其实是www.l.google.com。    
    hostent->h_aliases
    表示的是主机的别名.www.google.com就是google他自己的别名。有的时候,有的主机可能有好几个别名,这些,其实都是为了易于用户记忆而为自己的网站多取的名字。

    hostent->h_addrtype    
    表示的是主机ip地址的类型,到底是ipv4(AF_INET),还是pv6(AF_INET6)    

hostent->h_length      
    表示的是主机ip地址的长度

  ——————————————以上是分割线————————————————————————————

可见,通过gethostbyname 的方法获取IP的方法也是可以的,只是这个VMNet 8地址确实排在前面,我无法修改。

而且gethostbyname是递归通过DNS阻塞查询主机名和IP,很影响效率。据说好多公司做爬虫都是自己查询主机名和网站名的IP,不用gethostbyname,就是因为gethostbyname效率太低了。

继续查资料。

发现一个函数GetAdaptersInfo,getAdapterInfo相应参考资料如下:

 

GetAdaptersInfo函数:

DWORD GetAdaptersInfo(
  // 接受数据的缓冲区,指向一个结构IP_ADAPTER_INFO
  PIP_ADAPTER_INFO pAdapterInfo,  
  // 指向输出缓冲区的大小的指针
  PULONG pOutBufLen              
);

这个函数中最重要是第一个参数,看看这个结构有点啥?MSDN中说明如下:

typedef struct _IP_ADAPTER_INFO {
  struct _IP_ADAPTER_INFO* Next; // 链表指针,指向下一个单元
  DWORD ComboIndex;
  char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4]; // 接口信息物理名称
  char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4];// 接口描述信息
  UINT AddressLength; // MAC地址的长度
  BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH]; // MAC地址
  DWORD Index;
  UINT Type;
  UINT DhcpEnabled;
  PIP_ADDR_STRING CurrentIpAddress;
  IP_ADDR_STRING IpAddressList;  // IP地址列表
  IP_ADDR_STRING GatewayList;   // 网关地址列表
  IP_ADDR_STRING DhcpServer;   // DHCP地址
  BOOL HaveWins;
  IP_ADDR_STRING PrimaryWinsServer;   // 首选WINS 服务器
  IP_ADDR_STRING SecondaryWinsServer; // 备用WINS服务器
  time_t LeaseObtained;
  time_t LeaseExpires;
} IP_ADAPTER_INFO, *PIP_ADAPTER_INFO;

————————————————————以上是分割线————————————————————

于是,参照相应样例,写出如下代码:

#include
#include
#include

#pragma comment(lib, "IPHlpApi.Lib")

int main()
{
	IP_ADAPTER_INFO IOInfo[20];
	PIP_ADAPTER_INFO pIOInfo = NULL;
	DWORD Result = 0;
	unsigned long nLen = sizeof(IOInfo);

	Result = GetAdaptersInfo(IOInfo, &nLen);

	if (NO_ERROR != Result)
	{
		fprintf(stderr, "GetAdaptersInfo Error!\n");
		return -1;
	}
	else
	{
		pIOInfo = IOInfo;

		int Num = 0;
		while (pIOInfo != NULL)
		{
			fprintf(stdout, "\n--------------------Num.%d-------------------------\n", Num);
			printf("|Name:%s\n", pIOInfo->AdapterName);
			printf("|Desc:%s\n", pIOInfo->Description);
			printf("|IP:%s\n", pIOInfo->IpAddressList.IpAddress.String);
			printf("|MAC:%02X:%02X:%02X:%02X:%02X:%02X\n", pIOInfo->Address[0], pIOInfo->Address[1], pIOInfo->Address[2],
				pIOInfo->Address[3], pIOInfo->Address[4], pIOInfo->Address[5]);
			printf("|GateWay:%s\n", pIOInfo->GatewayList.IpAddress.String);
			printf("--------------------Num.%d-------------------------", Num++);

			pIOInfo = pIOInfo->Next;
		}
	}

	return 0;
}

运行结果如下:

多网卡情况下如何正确获得IP_第2张图片

我看到,运行程序确实区分出了VM的网卡和本地网卡;无奈本地网卡很有可能变,而本地网卡的Desc不足以填充足够的信息,从而为作为整个信息的区分。

我突然想到IPConfig命令,可以完全显示出网卡和网络的名称,于是,收启发,想进行IPConfig命令,然后获取信息,再获得IP地址。

在我的机器上运行ipconfig /all 命令,获得结果如下:

Windows IP 配置

   主机名  . . . . . . . . . . . . . : USER-20151221DQ
   主 DNS 后缀 . . . . . . . . . . . : 
   节点类型  . . . . . . . . . . . . : 混合
   IP 路由已启用 . . . . . . . . . . : 否
   WINS 代理已启用 . . . . . . . . . : 否

以太网适配器 本地连接 2:

   连接特定的 DNS 后缀 . . . . . . . : localdomain
   描述. . . . . . . . . . . . . . . : VMware Virtual Ethernet Adapter for VMnet8
   物理地址. . . . . . . . . . . . . : 00-50-56-C0-00-08
   DHCP 已启用 . . . . . . . . . . . : 是
   自动配置已启用. . . . . . . . . . : 是
   本地链接 IPv6 地址. . . . . . . . : fe80::643d:e61d:7f3:fb4d%15(首选) 
   IPv4 地址 . . . . . . . . . . . . : 192.168.233.1(首选) 
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   获得租约的时间  . . . . . . . . . : 2016年7月19日 9:26:32
   租约过期的时间  . . . . . . . . . : 2016年7月19日 13:41:32
   默认网关. . . . . . . . . . . . . : 
   DHCP 服务器 . . . . . . . . . . . : 192.168.233.254
   DHCPv6 IAID . . . . . . . . . . . : 318787670
   DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-01-1E-09-9D-1C-2C-41-38-8E-9C-F8
   DNS 服务器  . . . . . . . . . . . : 192.168.233.2
   主 WINS 服务器  . . . . . . . . . : 192.168.233.2
   TCPIP 上的 NetBIOS  . . . . . . . : 已启用

以太网适配器 VMware Network Adapter VMnet1:

   连接特定的 DNS 后缀 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : VMware Virtual Ethernet Adapter for VMnet1
   物理地址. . . . . . . . . . . . . : 00-50-56-C0-00-01
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是
   本地链接 IPv6 地址. . . . . . . . : fe80::acef:74f3:abe2:7f54%13(首选) 
   IPv4 地址 . . . . . . . . . . . . : 192.168.254.1(首选) 
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . : 
   DHCPv6 IAID . . . . . . . . . . . : 302010454
   DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-01-1E-09-9D-1C-2C-41-38-8E-9C-F8
   DNS 服务器  . . . . . . . . . . . : fec0:0:0:ffff::1%1
                                       fec0:0:0:ffff::2%1
                                       fec0:0:0:ffff::3%1
   TCPIP 上的 NetBIOS  . . . . . . . : 已启用

以太网适配器 本地连接:

   连接特定的 DNS 后缀 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Intel(R) 82579LM Gigabit Network Connection
   物理地址. . . . . . . . . . . . . : 2C-41-38-8E-9C-F8
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是
   本地链接 IPv6 地址. . . . . . . . : fe80::6822:7d71:8cda:2843%11(首选) 
   IPv4 地址 . . . . . . . . . . . . : 192.168.1.155(首选) 
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . : 192.168.1.1
   DHCPv6 IAID . . . . . . . . . . . : 237781304
   DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-01-1E-09-9D-1C-2C-41-38-8E-9C-F8
   DNS 服务器  . . . . . . . . . . . : fec0:0:0:ffff::1%1
                                       fec0:0:0:ffff::2%1
                                       fec0:0:0:ffff::3%1
   TCPIP 上的 NetBIOS  . . . . . . . : 已启用

隧道适配器 isatap.{A5D17715-BE7B-4932-9B76-1F96CFE71258}:

   媒体状态  . . . . . . . . . . . . : 媒体已断开
   连接特定的 DNS 后缀 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是

隧道适配器 isatap.{EFE41274-D6A2-40A2-B625-131EA423FCD6}:

   媒体状态  . . . . . . . . . . . . : 媒体已断开
   连接特定的 DNS 后缀 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter #2
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是

隧道适配器 isatap.localdomain:

   媒体状态  . . . . . . . . . . . . : 媒体已断开
   连接特定的 DNS 后缀 . . . . . . . : localdomain
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter #3
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是
-E0
   DHCP 已启用 . . . . .

通过这个,我可以清楚的看到,我的首个IP确实是命名为本地连接2  的 IP,而不是我的本地IP;但是这个上面的网络名称绝对不错,“本地连接”这个网络IP是:192.168.1.155

于是,用着执行这个ipconfig的方法的程序,发现,知道一个system函数。发现此函数可以运行命令,但是,显示不出来命令的回显。目前也找不到更好的方法

,于是,我想到的方法是,把命令回显存到相应文件里,然后,再进行读取后获得IP。

查询到一个函数GetCurrentDirectory,然后确保运行的程序创建的文件在相应的程序的路径下,可以进行灵活写入。

整个程序如下:

#include
#include
#include
#include
#include
//#include

#ifndef MAXTHPATH
#define MAXTHPATH (256)
#endif

#ifndef IPNUM
#define IPNUM (20)
#endif

#ifndef FILENAME
#define FILENAME ("/NetMask.txt")
#endif

char *getLocalIP(char *pszIP);

int main(int argc, char *argv[])
{
	char pszIP[IPNUM] = {0};
	getLocalIP(pszIP);
	printf("本地IP为 %s\n", pszIP);

	//delete []buf;

	return 0;
}

char *getLocalIP(char *pszIP)
{
	char pPath[MAXTHPATH];
	GetCurrentDirectory(MAXTHPATH, pPath);

	strcat(pPath, FILENAME);
	
	char pszCmd[MAXTHPATH] = {0};
	strcpy(pszCmd, "ipconfig /all > ");
	strcpy(pszCmd + strlen(pszCmd), pPath);
	
	//执行DOS命令,并把网络信息输出到文件夹下
	system(pszCmd);

	FILE *fp = fopen(pPath, "r");
	fseek(fp, 0, SEEK_END);
	int len = ftell(fp);
	rewind(fp);
	char *pBuf = new char[len+1];
	memset(pBuf, 0, len +1);
	fread(pBuf, 1, len, fp);
	fclose(fp);
	
	//根据一个机器获得,如果DOS命令变更,可能会发生更改
	char *pLAN = strstr(pBuf, "本地连接:");
	char *pIPV4 = strstr(pLAN, "IPv4 地址");
	pIPV4 = strstr(pIPV4, ":");
	pIPV4 += 2; //翻过“:”符号,获得真正的IP

	char *pEnd = strstr(pIPV4, "(");

	strncpy(pszIP, pIPV4, pEnd - pIPV4);

	//避免内存泄露
	delete []pBuf;

	return pszIP;
}

运行结果如下:

多网卡情况下如何正确获得IP_第3张图片

显然,得到了正确的IP。

几个值得注意的事情是:

1)在Linux下,此命令应该改为ifconfig;

2)其他机器不知道相应的回显字符串格式是否正确,还有,机器的语言由中文换成英文后,是否能有正确的支持;

3)system函数无法把回显直接输出到程序里;

4)最后获得IP的过程,读取字符串可以用正则;正则匹配通用性更好,但在windows下不支持正则,要想进行支持,需要添加boost库进行支持

5)有没有更好的直接获取本地IP的方法,虽然此种方法可以获得多网卡,尤其是实际的物理多网卡的IP地址

     

你可能感兴趣的:(多网卡情况下如何正确获得IP)