這幾天想複習下Windows C socket網絡編程,網上查閱了一些資料,大部分資料還是比較老的,介紹的都是舊接口函數,而且,絕大多數書上的內容都沒有介紹API中支持ipv6的接口。
我在寫一個獲得本地網卡信息的函數的時候,本來想用GetAdapterInfo,由於很久沒用過win32 api,於是查了下MSDN,發現這個接口已經很舊了,而且不支持ipv6,在文檔中明確推薦使用GetAdaptersAddresses接口,查了下,除了支持IPV6,使用方式跟GetAdapterInfo差別倒是不大,但是數據結構複雜很多,特別是獲得網卡信息的句柄結構體,挺複雜的,google了一下,想找幾個sample,搜了半天就只有msdn上那一個不太好的sample而已,在CSDN上搜了一下,資料也非常有限,有的人甚至說用這個函數無法獲得DNS地址,因此仍然推薦老的GetAdapterInfo,我自己試了下,還是可以獲得地址信息的,無論是硬件地址還是網絡地址,只不過麻煩一些,但都可以得到,而且還提供了很多額外信息,比GetAdapterInfo更全面的信息,因此這個接口函數在將來應該是會很有用。
這個函數的接口聲明是這樣的:
ULONG WINAPIGetAdaptersAddresses(
__in ULONGFamily,
__in ULONGFlags,
__in PVOIDReserved,
__inout PIP_ADAPTER_ADDRESSESAdapterAddresses,
__inout PULONG SizePointer
);
第一個參數Family是網絡協議族,用戶可以指定ipv6和ipv4,這是它和GetAdapterInfo接口區別最大的地方。第二個參數是指定地址類型的,可以指定單播、多播、ipv6、DNS等,用戶可以根據需求傳不同的參數得到不同的地址。第三個是保留參數,補足位用的。第四個參數AdapterAddresses是一個指向網卡信息結構體的指針,該指針類型是PIP_ADAPTER_ADDRESSES類型的,關於這個變量後面再詳細介紹。第五個參數是AdapterAddresses所需數據大小的值。
這幾個變量裏,AdapterAddresses是最核心的變量,裏面存儲着用戶所需要的信息,該變量定義非常長,結構體裏面有34個數據成員,涵蓋了網卡的全部信息,由於信息衆多,因此不一一介紹,只說幾個常用的數據成員。
FirstUnicastAddress,FirstAnycastAddress, FirstMulticastAddress; FirstDnsServerAddress,這幾個數據成員分別代表單播地址、任播地址、多播地址、DNS服務器地址。其中的anycast是隻有ipv6纔有的數據傳輸方式,而單播和多播則無協議限制,這幾種傳輸方式的區別可以在任意一本網絡教材上查到,這裏不做多餘敘述。這幾個變量全都是一個鏈表的頭結點,這是處於多網卡計算機的考量,通過頭結點,用戶可以枚舉出所有網卡的地址信息,網上有人說只能枚舉出三個網卡的信息,但我在win7上試過,枚舉出所有網卡是沒問題的,可能跟操作系統有關。
另一個比較常用的數據成員是PhysicalAddress,它是一個數組,該數據成員存儲了網卡的mac地址,而數組大小由PhysicalAddressLength指定。一般來說,該數組大小是6。
Description是一個PCHAR類型的變量,存儲着網卡的描述,比如11b/g/n。
OperStatus是描述網卡狀態的變量,是個枚舉類型IF_OPER_STATUS,該類型包含7種網卡的狀態,比如測試、激活、等待等。
除了以上信息,AdapterAddresses還提供了其他很多有用的信息,如果有興趣可以msdn上搜索一下。
對於熟悉使用GetAdapterInfo的程序員來說,GetAdaptersAddresses函數的使用並不複雜,下面是一個我個人寫的示例程序,演示該函數的基本功能:
#include<WinSock2.h>
#include<WS2tcpip.h>
#include<iostream>
#include<IPHlpApi.h>
using namespace std;
int main()
{
PIP_ADAPTER_ADDRESSES pAddresses = nullptr;
IP_ADAPTER_DNS_SERVER_ADDRESS *pDnServer = nullptr;
ULONG outBufLen = 0;
DWORD dwRetVal = 0;
char buff[100];
DWORD bufflen=100;
int i;
GetAdaptersAddresses(AF_UNSPEC,0, NULL, pAddresses,&outBufLen);
pAddresses = (IP_ADAPTER_ADDRESSES*) malloc(outBufLen);
if ((dwRetVal = GetAdaptersAddresses(AF_INET,GAA_FLAG_SKIP_ANYCAST,NULL,pAddresses,&outBufLen)) == NO_ERROR) {
while (pAddresses) {
printf("%S, %.2x-%.2x-%.2x-%.2x-%.2x-%.2x: \n",
pAddresses->FriendlyName,
pAddresses->PhysicalAddress[0],pAddresses->PhysicalAddress[1],
pAddresses->PhysicalAddress[2],pAddresses->PhysicalAddress[3],
pAddresses->PhysicalAddress[4],pAddresses->PhysicalAddress[5]);
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = pAddresses->FirstUnicastAddress;
pDnServer = pAddresses->FirstDnsServerAddress;
if(pDnServer)
{
sockaddr_in *sa_in = (sockaddr_in *)pDnServer->Address.lpSockaddr;
printf("DNS:%s\n",inet_ntop(AF_INET,&(sa_in->sin_addr),buff,bufflen));
}
if (pAddresses->OperStatus == IfOperStatusUp)
{
printf("Status: active\n");
}
else
{
printf("Status: deactive\n");
}
for (i = 0; pUnicast != NULL; i++)
{
if (pUnicast->Address.lpSockaddr->sa_family == AF_INET)
{
sockaddr_in *sa_in = (sockaddr_in *)pUnicast->Address.lpSockaddr;
printf("IPV4 Unicast Address:%s\n",inet_ntop(AF_INET,&(sa_in->sin_addr),buff,bufflen));
}
else if (pUnicast->Address.lpSockaddr->sa_family == AF_INET6)
{
sockaddr_in6 *sa_in6 = (sockaddr_in6 *)pUnicast->Address.lpSockaddr;
printf("IPV6:%s\n",inet_ntop(AF_INET6,&(sa_in6->sin6_addr),buff,bufflen));
}
else
{
printf("\tUNSPEC");
}
pUnicast = pUnicast->Next;
}
printf("Number of Unicast Addresses: %d\n", i);
pAddresses = pAddresses->Next;
}
}
else {
LPVOID lpMsgBuf;
printf("Call to GetAdaptersAddresses failed.\n");
if (FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dwRetVal,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL )) {
printf("\tError: %s", lpMsgBuf);
}
LocalFree( lpMsgBuf );
}
free(pAddresses);
return 0;
}
這個小程序沒什麼特別之處,可以輸出網卡描述,IP地址,MAC地址和DNS地址。但是,這個程序有幾處值得注意的地方,首先就是第一次調用GetAdaptersAddresses的地方,這個調用目的並不是獲得網卡信息句柄,而是獲得該結構體的大小值,在這裏也就是outBufLen,然後才能爲pAddresses分配內存空間,最後再再次調用GetAdaptersAddresses以獲得網卡信息的指針,這種使用方法有點怪異,但是我翻閱了一些資料,幾乎全是這麼幹的。還有一處是由FirstUnicastAddress獲得網絡地址信息的地方,由這個數據成員可以得到包含地址信息的值,在這裏就是lpSockaddr,這個變量是sockaddr類型的,我之前曾經試圖直接輸出這個變量來獲得地址信息,後來查閱了一些資料才知道,這個變量是操作系統數據類型,並不是給用戶用的,該數據之所以沒格式,是爲了適應不同操作系統而避免數據不通用。用戶想要初始化或者使用這個數據,必須把它轉化成XXX_in類型的數據,如果是ipv4地址,需要轉化成sockaddr_in格式數據,而ipv6類型的地址則需要轉化成sockaddr_in6格式的數據。轉化完之後,工作還沒完,還必須用InetNtop把數據轉化成string類型的數據以便用戶操作,因此把原始格式轉化成用戶可見的格式需要以上兩步操作。