最近有個需求必須在C++中取得本地的IP,包括IPv6與IPv4兩種的IP,查來查去就只有GetAdaptersAddresses API比較合用,但是使用上卻不是很容易,這篇將之稍做整理。
GetAdaptersAddresses API在使用時必須先將相關的函式庫與標頭擋設定好,必須設定靜態函式庫Iphlpapi.lib與Iphlpapi.h標頭擋,像是下面這樣,靜態函式庫若有需要也可以透過專案屬性設定。
…
#include <iphlpapi.h>
…
#pragma comment(lib, “IPHLPAPI.lib”)
準備動作設定完成就可以使用GetAdaptersAddresses API了,但在使用前我們必須對GetAdaptersAddresses API的參數做些深入的了解,才會知道我們必須帶入哪些參數,這可以直接查閱API的函式原型:
ULONG WINAPI GetAdaptersAddresses(
in ULONG Family, in ULONG Flags,
in PVOID Reserved, inout PIP_ADAPTER_ADDRESSES AdapterAddresses,
__inout PULONG SizePointer
);
從函式原型中我們可以看出,GetAdaptersAddresses API必須帶入五個參數,前兩個參數是我們要設定的Flag,是要IPv4還是IPv6?是不是某些裝置要被忽略?或是提供一些相關的設定,第三個參數是系統保留的參數,固定帶NULL就可以了,第四個參數是問回來的網卡資訊,是我們主要要看的部分,而最後一個參數是空間大小,會回傳告知我們必須提供多少的記憶體空間。
第一個參數是用來決定感興趣的是IPv4還是IPv6的資訊,它能接受的Flag有下表列的這些,若帶入的是AF_UNSPEC代表IPv4跟IPv6都想要知道,帶入AF_INET代表只對IPv4感興趣,而帶入AF_INET6則是想查閱的只有IPv6。
Value | Meaning | |
AF_UNSPEC | 0 | Return both IPv4 and IPv6 addresses associated with adapters with IPv4 or IPv6 enabled. |
AF_INET | 2 | Return only IPv4 addresses associated with adapters with IPv4 enabled. |
AF_INET6 | 23 | Return only IPv6 addresses associated with adapters with IPv6 enabled. |
第二個參數可設定某些裝置要被忽略,或是提供一些相關的設定,像是可以設定忽略DNS Server的資訊等等,這邊不多做介紹,可直接參閱下表:
Value | Meaning | |
GAA_FLAG_SKIP_UNICAST | 0x0001 | Do not return unicast addresses. |
GAA_FLAG_SKIP_ANYCAST | 0x0002 | Do not return IPv6 anycast addresses. |
GAA_FLAG_SKIP_MULTICAST | 0x0004 | Do not return multicast addresses. |
GAA_FLAG_SKIP_DNS_SERVER | 0x0008 | Do not return addresses of DNS servers. |
GAA_FLAG_INCLUDE_PREFIX | 0x0010 |
|
GAA_FLAG_SKIP_FRIENDLY_NAME | 0x0020 | Do not return the adapter friendly name. |
GAA_FLAG_INCLUDE_WINS_INFO | 0x0040 |
|
GAA_FLAG_INCLUDE_GATEWAYS | 0x0080 |
|
GAA_FLAG_INCLUDE_ALL_INTERFACES | 0x0100 |
|
GAA_FLAG_INCLUDE_ALL_COMPARTMENTS | 0x0200 |
|
GAA_FLAG_INCLUDE_TUNNEL_BINDINGORDER | 0x0400 | Return the adapter addresses sorted in tunnel binding order. This flag is supported on Windows Vista and later. |
在使用上通常習慣會呼叫兩次GetAdaptersAddresses API,第一次呼叫時會先將第四個參數帶NULL值,因為這次的叫用主要是要取得要多少的空間才夠存放網卡資訊,像下面這樣叫用需要的記憶體空間大小就會寫到最後一個參數。
ULONG outBufLen = 0;
GetAdaptersAddresses(AF_UNSPEC, 0, NULL, NULL, &outBufLen);
取得了所需的空間大小後,透過malloc指派足夠的記憶體,再次叫用GetAdaptersAddresses API取得網卡資訊:
PIP_ADAPTER_ADDRESSES pAddresses = (IP_ADAPTER_ADDRESSES*) malloc(outBufLen);
GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST, NULL, pAddresses, &outBufLen);
取得了網卡資訊後,網卡資訊會是IP_ADAPTER_ADDRESSES structure的型態,以Linked List的方式存放,因此在取用時必須遍尋Linked List每個元素,取出我們需要的IP位置,其中比較麻煩的就是IP位置是以SOCKET_ADDRESS structure型態存放,要透過inet_ntop function轉換為字串,程式寫起來就像下面這樣:
…
char buff[100];
DWORD bufflen=100;
PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL;
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;
LPSOCKADDR addr = NULL;
pCurrAddresses = pAddresses;
while (pCurrAddresses)
{
if(pCurrAddresses->OperStatus != IfOperStatusUp)
{
pCurrAddresses = pCurrAddresses->Next;
continue;
}
pUnicast = pCurrAddresses->FirstUnicastAddress;
while (pUnicast)
{
addr = pUnicast->Address.lpSockaddr;
ZeroMemory(buff, bufflen);
if (addr->sa_family == AF_INET6)
{
sockaddr_in6 *sa_in6 = (sockaddr_in6 *)addr;
inet_ntop(AF_INET6, &(sa_in6->sin6_addr), buff, bufflen);
}else
{
sockaddr_in *sa_in = (sockaddr_in *)addr;
inet_ntop(AF_INET, &(sa_in->sin_addr), buff, bufflen);
}
localIPs.push_back(buff);
pUnicast = pUnicast->Next;
}
pCurrAddresses = pCurrAddresses->Next;
}
free(pAddresses);
...</pre>
最後附上完整的範例:
// Test_LocalIPs.cpp : Defines the entry point for the console application.
//
#include “stdafx.h”
#include <winsock2.h>
#include <WS2tcpip.h>
#include <iphlpapi.h>
#include <vector>
#include <string>
#include <algorithm>
// Link with Iphlpapi.lib
#pragma comment(lib, “Ws2_32.lib”)
#pragma comment(lib, “IPHLPAPI.lib”)
using namespace std;
vector<string> GetLocalIPs()
{
vector<string> localIPs;
PIP_ADAPTER_ADDRESSES pAddresses = NULL;
ULONG outBufLen = 0;
GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAddresses, &outBufLen);
pAddresses = (IP_ADAPTER_ADDRESSES*) malloc(outBufLen);
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST, NULL, pAddresses, &outBufLen) != NO_ERROR)
{
free(pAddresses);
return localIPs;
}
char buff[100];
DWORD bufflen=100;
PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL;
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;
LPSOCKADDR addr = NULL;
pCurrAddresses = pAddresses;
while (pCurrAddresses)
{
if(pCurrAddresses->OperStatus != IfOperStatusUp)
{
pCurrAddresses = pCurrAddresses->Next;
continue;
}
pUnicast = pCurrAddresses->FirstUnicastAddress;
while (pUnicast)
{
addr = pUnicast->Address.lpSockaddr;
ZeroMemory(buff, bufflen);
if (addr->sa_family == AF_INET6)
{
sockaddr_in6 *sa_in6 = (sockaddr_in6 *)addr;
inet_ntop(AF_INET6, &(sa_in6->sin6_addr), buff, bufflen);
}else
{
sockaddr_in *sa_in = (sockaddr_in *)addr;
inet_ntop(AF_INET, &(sa_in->sin_addr), buff, bufflen);
}
localIPs.push_back(buff);
pUnicast = pUnicast->Next;
}
pCurrAddresses = pCurrAddresses->Next;
}
free(pAddresses);
return localIPs;
}
int _tmain(int argc, _TCHAR* argv[])
{
vector<string> localIPs = GetLocalIPs();
for_each (localIPs.begin(), localIPs.end(), &
{
char szTmp[255];
sprintf(szTmp, “%s\r\n”, ip.c_str());
printf(szTmp);
});
return 0;
}
運行後可得到像下面這樣的結果:
Link
GetAdaptersAddresses function
IP_ADAPTER_ADDRESSES structure
IP_ADAPTER_UNICAST_ADDRESS structure
SOCKET_ADDRESS structure