Qt中暂时据我了解暂时没有对底层网卡操作的类和相关库,这次通过ARP协议写的局域网ip搜索程序都是采用微软的底层网卡操作相关库,此次主要了libpacket.a和libwpcap.a库。操作步骤如下:
(1) 首先可以在添加必要的库文件,可以在官网http://www.winpcap.org/install/default.htm下载最新的WinPcap安装包,并安装,运行基于winpcap的程序需要其支持。
(2) 到http://www.winpcap.org/devel.htm 下载开发包,把winpcap开发包中的Include目录下的所有内容拷贝到qt的\Qt\4.8.1\mingw\include目录下。
(3) 把winpcap开发包中的Lib目录下的Packet.a和wpcap.a拷贝到\Qt\4.8.1\mingw\lib目录下。但是我感觉那样没有成功,我是拷贝到工程目录下;再在工程头文件中添加库如下代码:
LIBS+=$$PWD/libpacket.a\
$$PWD/libwpcap.a
还有就是要在库文件中添加#include"Packet32.h"头文件(参考网址http://blog.csdn.net/q5707802/article/details/38373929)
(4) 这样相关的库和头文件都准备好了开始程序编写。实现ARP数据包的发送和接收主要为获取本机网卡信息、打开网卡、将封装好的数据包发送出去、接收返回的ARP数据包、数据解析、关闭网卡等步骤。步骤看着不多,但是里面有很多细节实现起来还是比较麻烦。也是费了很大的力气才把程序调试好。下面一步一步来讲解Qt下实现ip搜索的步骤
(5) 获取网卡信息
在IP搜索程序中主要用到了网卡名称,网卡的MAC地址,IP到是没有用到。这里主要有两种方法实现网卡信息的获取。其一通过Packet32.h中的函数PacketGetAdapterNames()函数获取,这个还没有尝试过;其二通过Qt自带的类QNetworkInterface,这个类可以获取网卡的所有信息,代码实现如下
QList<QNetworkInterface>listMac=netWorkInerface.allInterfaces();
这一句代码就把网卡的所有信息都获取到了,现在只需要通过listMac输出即可,例如
listMac.at(0).hardwareAddress();//获取本机MAC
localMacName=listMac.at(0).name();//获取本机MAC名称
使用起来很方便,这样就获取到了网卡信息。但是这时还要对获取到网卡信息进行转换,网卡MAC要转换成16进制数才能进行下一步的操作。这里要提示的是网卡名称其实可以不用做其它操作就可以在下面的步骤中使用了,但是通过packet库发送ARP数据包在打开网卡时使用NPF方式,所以在获取到网卡名称中后添加如下代码:
localMacName="\\Device\\NPF_"+localMacName;
(6) 打开网卡
打开网卡到是很简答,通过PacketOpenAdapter(网卡名称),如果打开成功返回打开的网卡句柄,如果打开失败返回NULL。这里提示一定要保存次网卡句柄,以便在后面使用
(7) 封装数据包
这里比较重要的是要把封装的数据包的格式转换为运输层要发送的数据包格式,可以通过htons()函数实现,htons主要功能是将无符号短整型转换成网络字节。
(8) 发送数据包
这里将用到两个比较重要的结构体
数据结构:_ADAPTER(关于Network Adapter的)
typedef struct _ADAPTER
{
HANDLE hFile; // 一个打开的NPF driver实例的句柄:
CHAR SymbolicLink[MAX_LINK_NAME_LENGTH]; // 当前打开的网卡的名字:
int NumWrites; // 在这块Adapter上,一个数据包被写的次数:
HANDLE ReadEvent; /* 这块Adapter上的read操作的通知事件。它可以被传递给标准Win32函数(如WaitForSingleObject或者WaitForMultipleObjects),这样可以等待到driver的缓冲区内有数据到来。在同时等待几个事件的GUI程序中,它特别有用。在Windows2000/XP中,函数PacketSetMinToCopy()可以用来设置内核缓冲区中激发本事件的最小数据大小:*/
UINT ReadTimeOut; // 设置一个时间,到时候,即使没有捕获任何包,read操作也会被释放,ReadEvent也会被触发:
} ADAPTER, *LPADAPTER;
数据结构:_PACKET(关于Packet的)
typedef struct _PACKET
{
HANDLE hEvent; // 向后兼容用的:
OVERLAPPED OverLapped; // 向后兼容用的:
PVOID Buffer; // 存放Packets的缓冲区:
UINT Length; // 缓冲区的大小:
DWORD ulBytesReceived; // 当前缓冲区中有效的字节数,如,上一次调用PacketReceivePacket()函数接收到的字节数:
BOOLEAN bIoComplete // 向后兼容用的:
} PACKET, *LPPACKET;
发送代码如下,发送前提是数据包已经封装好了
lpPacket=PacketAllocatePacket();//给PACKET结构指针分配内存
if(lpPacket==NULL)
{
PacketCloseAdapter(adapterHandle);
returnfalse;
}
PacketInitPacket(lpPacket,&broadcastARPcmd,sizeof(broadcastARPcmd));
if(PacketSetNumWrites(adapterHandle,1)==0) //每次只发送一个包
{
PacketFreePacket(lpPacket);
PacketCloseAdapter(adapterHandle);
returnfalse;
}
BOOLflag=PacketSendPacket(adapterHandle,lpPacket,true);
//判断是否发送成功
if(flag==FALSE)
{
returnfalse;
}
这里面有几个函数PacketAllocatePacket()给PACKET结构指针分配内存,这个函
函数必须要要不然数据发送不出去。
PacketInitPacket(lpPacket,&broadcastARPcmd,sizeof(broadcastARPcmd))其中lpPacket为_PACKET结构体指针,broadcastARPcmd为要发送数据包,这个函数必须有,PacketSetNumWrites()设置发送次数可以没有,默认发送一次。最后通过PacketSendPacket(adapterHandle,lpPacket,true)发送数据包,其中adapterHandle为网卡句柄,第二个参数说过了,第三个参数不用说了很明白。
这样数据包就发送出去了。我也是调试了很长时间才调试好的。
(9)解析数据包
前面的步骤完了算是做完了一大半了,解析数据包主要是对ARP广播信号非回应信息解析。这里在winpcap开发包中有参考例子,可以进行参考的。我也是参考例程代码的。这里不在详细介绍。相关参考代码如下:
if(PacketSetHwFilter(adapterHandle,0x00000020)==FALSE)
{
qDebug("Warning:unabletosetpromiscuousmode!\n");
returnfalse;
}
if(PacketSetBuff(adapterHandle,500*1024)==false) //设置网卡缓存大小
{
PacketFreePacket(lpPacket);
PacketCloseAdapter(adapterHandle);
returnfalse;
}
if(PacketSetReadTimeout(adapterHandle,100)==false) //设置超时时间
{
PacketFreePacket(lpPacket);
PacketCloseAdapter(adapterHandle);
returnfalse;
}
PacketInitPacket(lpPacket,(char*)recBuf,sizeof(recBuf));
while(readNULLFlag)
{
tempresult=PacketReceivePacket(adapterHandle,lpPacket,TRUE);//某个网卡来接受
if(tempresult==FALSE||lpPacket->ulBytesReceived<1)
{
tempresult=PacketReceivePacket(adapterHandle,lpPacket,TRUE);
if(tempresult==FALSE||lpPacket->ulBytesReceived<1)
{
readNULLFlag=false;
}
}
receiveDataPacket(lpPacket);
}
这里说明的是PacketSetHwFilter(adapterHandle,0x00000020)中的0x00000020,这里设置过滤模式为混杂模式,读取网卡上收到的所有信息,在MFC中用的是宏定义,我在QT下查找时没有找到于是在MFC中查找的宏定义的值直接复制过来的。其中宏定义为
#define NDIS_PACKET_TYPE_DIRECTED 0x00000001
#define NDIS_PACKET_TYPE_MULTICAST 0x00000002
#define NDIS_PACKET_TYPE_ALL_MULTICAST 0x00000004
#define NDIS_PACKET_TYPE_BROADCAST 0x00000008
#define NDIS_PACKET_TYPE_SOURCE_ROUTING 0x00000010
#define NDIS_PACKET_TYPE_PROMISCUOUS 0x00000020
#define NDIS_PACKET_TYPE_SMT 0x00000040
#define NDIS_PACKET_TYPE_ALL_LOCAL 0x00000080
#define NDIS_PACKET_TYPE_GROUP 0x00001000
#define NDIS_PACKET_TYPE_ALL_FUNCTIONAL 0x00002000
#define NDIS_PACKET_TYPE_FUNCTIONAL 0x00004000
#define NDIS_PACKET_TYPE_MAC_FRAME 0x00008000
#define NDIS_PACKET_TYPE_NO_LOCAL 0x00010000
其它宏定义自己可以网上查看相关说明不在一一说明。这些都设置完成后就可通过PacketReceivePacket()函数接收数据,最后通过receiveDataPacket()函数进行解析,这个函数是自己定义实现的。相关代码在 winpcap 开发包中有参考例子可供参考