出处:https://www.cnblogs.com/L-hq815/archive/2012/08/04/2622829.html
一台机器上可能不只有一个网卡,但每一个网卡只有一个MAC地址,而每一个网卡可能配置有多个IP地址;如平常的笔记本电脑中,就会有无线网卡和有线网卡(网线接口)两种;因此,如果要获得本机所有网卡的IP和MAC地址信息,则必须顺序获得每个网卡,再依次获取其信息等;在windows sdk中,用IP_ADAPTER_INFO结构体存储网卡信息,包括网卡名、网卡描述、网卡MAC地址、网卡IP等,该结构体的主要描述如下所示:
typedef struct _IP_ADAPTER_INFO { struct _IP_ADAPTER_INFO* Next;//指向链表中下一个适配器信息的指针 DWORD ComboIndex;//预留值 char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4];//使用ANSI字符串表示的适配器名称 char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4];//使用ANSI字符串表示的适配器描述 UINT AddressLength;//适配器硬件地址以字节计算的长度 BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH];//硬件地址以BYTE数组所表示 DWORD Index;//适配器索引 UINT Type;//适配器类型,主要有以下几种: /* * MIB_IF_TYPE_OTHER 1 * MIB_IF_TYPE_ETHERNET 6 * MIB_IF_TYPE_TOKENRING 9 * MIB_IF_TYPE_FDDI 15 * MIB_IF_TYPE_PPP 23 * MIB_IF_TYPE_LOOPBACK 24 * MIB_IF_TYPE_SLIP 28 */ UINT DhcpEnabled;//指定这个适配器是否开启DHCP PIP_ADDR_STRING CurrentIpAddress;//预留值 IP_ADDR_STRING IpAddressList;//该适配器的IPv4地址链表 IP_ADDR_STRING GatewayList;//该适配器的网关IPv4地址链表 IP_ADDR_STRING DhcpServer;//该适配器的DHCP服务器的IPv4 地址链表 BOOL HaveWins; IP_ADDR_STRING PrimaryWinsServer; IP_ADDR_STRING SecondaryWinsServer; time_t LeaseObtained; time_t LeaseExpires; } IP_ADAPTER_INFO,*PIP_ADAPTER_INFO;
由于可能有多个网卡,因此struct _IP_ADAPTER_INFO* Next字段为一个链表结构指针,由于一个网卡可能有多个IP,因此IP_ADDR_STRING字段应该也是一个链表结构,其信息如下所示:
typedef struct _IP_ADDR_STRING { struct _IP_ADDR_STRING* Next; //指向同类型节点,即下一个IP(如果有多IP的话) IP_ADDRESS_STRING IpAddress; //IP地址信息 IP_MASK_STRING IpMask; //IP子网掩码 DWORD Context;// 网络表入口。这个值对应着AddIPAddredd和DeleteIPAddress函数中的NTEContext参数 } IP_ADDR_STRING;
在基本了解以上信息后,就可以调用GetAdaptersInfo函数来获取相关网卡信息了,其通用的代码如下所示:
#include <WinSock2.h> #include <Iphlpapi.h> #include <iostream> using namespace std; #pragma comment(lib,"Iphlpapi.lib") //需要添加Iphlpapi.lib库 int main(int argc, char* argv[]) { //PIP_ADAPTER_INFO结构体指针存储本机网卡信息 PIP_ADAPTER_INFO pIpAdapterInfo = new IP_ADAPTER_INFO(); //得到结构体大小,用于GetAdaptersInfo参数 unsigned long stSize = sizeof(IP_ADAPTER_INFO); //调用GetAdaptersInfo函数,填充pIpAdapterInfo指针变量;其中stSize参数既是一个输入量也是一个输出量 int nRel = GetAdaptersInfo(pIpAdapterInfo,&stSize); //记录网卡数量 int netCardNum = 0; //记录每张网卡上的IP地址数量 int IPnumPerNetCard = 0; if (ERROR_BUFFER_OVERFLOW == nRel) { //如果函数返回的是ERROR_BUFFER_OVERFLOW //则说明GetAdaptersInfo参数传递的内存空间不够,同时其传出stSize,表示需要的空间大小 //这也是说明为什么stSize既是一个输入量也是一个输出量 //释放原来的内存空间 delete pIpAdapterInfo; //重新申请内存空间用来存储所有网卡信息 pIpAdapterInfo = (PIP_ADAPTER_INFO)new BYTE[stSize]; //再次调用GetAdaptersInfo函数,填充pIpAdapterInfo指针变量 nRel=GetAdaptersInfo(pIpAdapterInfo,&stSize); } if (ERROR_SUCCESS == nRel) { //输出网卡信息 //可能有多网卡,因此通过循环去判断 while (pIpAdapterInfo) { cout<<"网卡数量:"<<++netCardNum<<endl; cout<<"网卡名称:"<<pIpAdapterInfo->AdapterName<<endl; cout<<"网卡描述:"<<pIpAdapterInfo->Description<<endl; switch(pIpAdapterInfo->Type) { case MIB_IF_TYPE_OTHER: cout<<"网卡类型:"<<"OTHER"<<endl; break; case MIB_IF_TYPE_ETHERNET: cout<<"网卡类型:"<<"ETHERNET"<<endl; break; case MIB_IF_TYPE_TOKENRING: cout<<"网卡类型:"<<"TOKENRING"<<endl; break; case MIB_IF_TYPE_FDDI: cout<<"网卡类型:"<<"FDDI"<<endl; break; case MIB_IF_TYPE_PPP: printf("PP\n"); cout<<"网卡类型:"<<"PPP"<<endl; break; case MIB_IF_TYPE_LOOPBACK: cout<<"网卡类型:"<<"LOOPBACK"<<endl; break; case MIB_IF_TYPE_SLIP: cout<<"网卡类型:"<<"SLIP"<<endl; break; default: break; } cout<<"网卡MAC地址:"; for (DWORD i = 0; i < pIpAdapterInfo->AddressLength; i++) if (i < pIpAdapterInfo->AddressLength-1) { printf("%02X-", pIpAdapterInfo->Address[i]); } else { printf("%02X\n", pIpAdapterInfo->Address[i]); } cout<<"网卡IP地址如下:"<<endl; //可能网卡有多IP,因此通过循环去判断 IP_ADDR_STRING *pIpAddrString =&(pIpAdapterInfo->IpAddressList); do { cout<<"该网卡上的IP数量:"<<++IPnumPerNetCard<<endl; cout<<"IP 地址:"<<pIpAddrString->IpAddress.String<<endl; cout<<"子网地址:"<<pIpAddrString->IpMask.String<<endl; cout<<"网关地址:"<<pIpAdapterInfo->GatewayList.IpAddress.String<<endl; pIpAddrString=pIpAddrString->Next; } while (pIpAddrString); pIpAdapterInfo = pIpAdapterInfo->Next; cout<<"--------------------------------------------------------------------"<<endl; } } //释放内存空间 if (pIpAdapterInfo) { delete pIpAdapterInfo; } return 0; }
执行结果:
出处:http://blog.sina.com.cn/s/blog_93b1c45d0101g4wk.html
这几天想复习下Windows C socket网络编程,网上查阅了一些资料,大部分资料还是比较老的,介绍的都是旧接口函数,而且,绝大多数书上的内容都没有介绍API中支持ipv6的接口。
我在写一个获得本地网卡信息的函数的时候,本来想用GetAdapterInfo,由于很久没用过win32 api,于是查了下MSDN,发现这个接口已经很旧了,而且不支持ipv6,在文档中明确推荐使用GetAdaptersAddresses接口,查了下,除了支持IPV6,使用方式跟GetAdapterInfo差别倒是不大,但是数据结构复杂很多,特别是获得网卡信息的句柄结构体,挺复杂的,google了一下,想找几个sample,搜了半天就只有msdn上那一个不太好的sample而已,在CSDN上搜了一下,资料也非常有限,有的人甚至说用这个函数无法获得DNS地址,因此仍然推荐老的GetAdapterInfo,我自己试了下,还是可以获得地址信息的,无论是硬件地址还是网络地址,只不过麻烦一些,但都可以得到,而且还提供了很多额外信息,比GetAdapterInfo更全面的信息,因此这个接口函数在将来应该是会很有用。
这个函数的接口声明是这样的:
ULONG WINAPIGetAdaptersAddress
);
第一个参数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上搜索一下。
- #include
- #include
- #include
- #include
- 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类型的数据以便用户操作,因此把原始格式转化成用户可见的格式需要以上两步操作。
MIB_IFTABLE结构,获取网卡数据流量
出处:http://blog.sina.com.cn/s/blog_77538e140101b7fb.html
#include
#pragma comment ( lib, "iphlpapi.lib")
使用GetIfTable()获取各个端口信息的时候用到的 _MIB_IFTABLE结构:
typedef struct _MIB_IFTABLE {
DWORD dwNumEntries; //得到的端口个数
MIB_IFROW table[ANY_SIZE]; //得到的各个端口的信息,这个结构才是重点
} MIB_IFTABLE, *PMIB_IFTABLE;
typedef struct _MIB_IFROW { WCHAR wszName[MAX_INTERFACE_NAME_LEN]; DWORD dwIndex; DWORD dwType; DWORD dwMtu; DWORD dwSpeed; DWORD dwPhysAddrLen; BYTE bPhysAddr[MAXLEN_PHYSADDR]; DWORD dwAdminStatus; DWORD dwOperStatus; DWORD dwLastChange; DWORD dwInOctets; DWORD dwInUcastPkts; DWORD dwInNUcastPkts; DWORD dwInDiscards; DWORD dwInErrors; DWORD dwInUnknownProtos; DWORD dwOutOctets; DWORD dwOutUcastPkts; DWORD dwOutNUcastPkts; DWORD dwOutDiscards; DWORD dwOutErrors; DWORD dwOutQLen; DWORD dwDescrLen; BYTE bDescr[MAXLEN_IFDESCR]; } MIB_IFROW, *PMIB_IFROW;
wzsName:包含了该接口的名字(多字节的),具体也不知道哈意思,就是一串数字,有懂的和我说一下 dwIndex:该接口的索引值,比如有多个网卡的时候,每个网卡都有一个索引值,是会随着网卡正在被使用的个数变化的 dwType:该接口的类型,这个类型是被IANA(是个什么协会吧)定义的,有以下几种:
其中24是网络回路的网卡(我自己是这样叫的),就是127.0.0.1那个,应该是每个机子都有的吧 一般我们用的是6. dwMtu:百度一下MTU就知道了,就是该接口的最大传输单元,理解为该通信协议的某一层上面能通过的最大的数据包的大小(以字节为单位) dwSpeed:该接口最大的传输速率,可是看成是这个接口每秒最多传多大的数据的一个规格,我刚开始的时候以为这是该接口的即时传输速度呢,郁闷 dwPhysAddrLen: bPhysAddr指向的地址的长度 bPhysAddr:指向该接口地址的指针 dwAdminStatus:该接口的管理状态,按我的理解就是人为设定的那个状态:启用/禁用 dwOperStatus:该接口的操作状态,它可以取以下的值,看了下面的值就知道是什么意思了 0 MIB_IF_OPER_STATUS_NON_OPERATIONAL 网络适配器被禁止的状态; 1 MIB_IF_OPER_STATUS_UNREACHABLE 没有连接的状态; 2 MIB_IF_OPER_STATUS_DISCONNECTED 电缆未连接的状态; 3 MIB_IF_OPER_STATUS_CONNECTING 广域网适配器连接中的状态; 4 MIB_IF_OPER_STATUS_CONNECTED 广域网适配器连接上远程对等点的状态; 5 MIB_IF_OPER_STATUS_OPERATIONAL 局域网适配器默认的连接状态; dwLastChange: 适配器状态最后一次改变的时间; dwInOctets: 该接口总的收到的数据大小; dwInUcastPkts As Long '总共收到(unicast包) dwInNUcastPkts As Long '总共收到(non-unicast包),包括广播包和多点传送包dwInDiscards As Long '收到后丢弃包总数(即使没有错误) dwInErrors As Long '收到出错包总数 dwInUnknownProtos As Long '收到后因协议不明而丢弃的包总数 dwOutOctets As Long '总共发送(字节) dwOutUcastPkts As Long '总共发送(unicast包) dwOutNUcastPkts As Long '总共发送(non-unicast包),包括广播包和多点传送包 dwOutDiscards As Long '发送丢弃包总数(即使没有错误) dwOutErrors As Long '发送出错包总数 dwOutQLen As Long '发送队列长度 dwDescrLen As Long ' bDescr部分有效长度 bDescr(0 To 255) As Byte '接口描述 也就是在设备管理器上看的到名字 用一秒钟前后得到的dwInOctets数据相减,就是这一秒中该接口的流量,用这个方法就可以计算机子的即时流量了 但是当机子有多个网卡时,怎么判断目前机子正在用的是哪一个,我也不知道,在网上看好多例子,当有多网卡时,都是直接用第一个来计算流量的,难道默认得到的结构数组第一个就是正在用的? 好像是这样的,如果有更详细的,留下说明吧