10.1.2 数据包接收的实例
适配器被打开后,就可进行数据包捕获了。下面的实例程序会将捕获的每个数据包的时间戳和长度从pcap_pkthdr结构体中解析出来,并打印。三个实例会分别使用回调函数(pcap_loop函数)、非回调函数(pcap_next_ex函数)与带有数据包过滤器的三种接收方式来进行数据包的捕获操作。
1.使用回调函数接收数据包的实例
该实例使用回调函数(pcap_loop函数)的接收方式来捕获数据包。该实例源代码如下,完整的代码见[ch10/read_loop工程]:
defne WIN32
defne HAVE_REMOTE
include<stdio.h>
include"pcap.h"
include<winsock.h>
/捕获数据包的回调函数/
void packet_handler(u_char*param,
const struct pcap_pkthdrheader,const u_charpkt_data);
int main()
{
pcap_t*adhandle;
……
/打开本机一个合适的网络设备/
……
pcap_loop(adhandle,0,packet_handler,NULL);
return 0;
}
/每次捕获到数据包时,自动调用该回调函数/
void packet_handler(u_charparam,const struct pcap_pkthdrheader,
const u_char*pkt_data)
{
struct tm*ltime;
char timestr[16];
time_t local_tv_sec;
//将时间戳转换成可识别的格式
local_tv_sec=header->ts.tv_sec;
ltime=localtime(&local_tv_sec);
strftime(timestr,sizeof timestr,"%H:%M:%S",ltime);
//打印数据包的捕获时间与数据包的长度
printf("%s,%.6d len:%d\n",
timestr,header->ts.tv_usec,header->len);
}
上述代码中,packet_handler函数是捕获数据包的回调函数,当用户调用pcap_dispatch函数或pcap_loop函数时,数据包会通过该回调函数传递给应用程序。该函数原型如下:
typedef void()pcap_handler(u_charuser,
const struct pcap_pkthdrpkt_header,const u_charpkt_data)
上述函数中,参数user是一个用户定义的参数,包含了捕获会话的状态,其对应于pcap_dispatch函数与pcap_loop函数的user参数;参数pkt_header是NPF驱动程序给数据包附加的一个信息头,而不是一个协议头;参数pkt_data指向数据包的数据,包括协议头。
此实例程序的运行结果如图10-1所示。
图 10-1 使用回调函数接收数据包
2.使用非回调函数接收数据包的实例
这一节中实例程序所实现的功能和执行结果与上一节相似,只是本实例将以pcap_next_ex函数代替上一节的pcap_loop函数。
pcap_loop函数是基于回调的原理来进行数据捕获的,有时候使用回调函数接收数据包并不实用——比如它会增加程序的复杂度,特别是在多线程的C++程序中。同时,使用pcap_loop函数可能会遇到障碍,主要因为它是直接由NPF驱动程序所调用的,因此应用程序不能直接控制它。这时,我们可以通过直接调用pcap_next_ex函数来获得一个数据包——只有当编程人员使用pcap_next_ex函数时才能接收到数据包,这也可提高程序的可读性。函数pcap_next_ex的参数和回调函数packet_handler的参数是一样的。
在该实例中,将会用到与上一节回调函数packet_handler中的类似代码,只是这里将它放入了main函数中,并结合调用了pcap_next_ex函数。实例具体源代码如下,完整的代码见[ch10/read_noloop工程]:
defne WIN32
defne HAVE_REMOTE
include<stdio.h>
include"pcap.h"
include<winsock.h>
int main()
{
pcap_t*adhandle;
……
/打开本机一个合适的网络设备/
……
/开始捕获/
struct pcap_pkthdr*header;
const u_char*pkt_data;
int res=-1;
struct tm*ltime;
char timestr[16];
time_t local_tv_sec;
/获取数据包/
while((res=pcap_next_ex(adhandle,&header,&pkt_data))>=0)
{
if(res==0)
//超时时间到
continue;
//将时间戳转换成可识别的格式*/
local_tv_sec=header->ts.tv_sec;
ltime=localtime(&local_tv_sec);
strftime(timestr,sizeof timestr,"%H:%M:%S",ltime);
//打印数据包的捕获时间与数据包的长度
printf("%s,%.6d len:%d\n",
timestr,header->ts.tv_usec,header->len);
}
if(res==-1){//处理读取错误
printf("Error reading the packets:%s\n",
pcap_geterr(adhandle));
pcap_close(adhandle);
return-1;
}
pcap_close(adhandle);
return 0;
}
实例程序运行的结果如图10-2所示。
图 10-2 使用非回调函数接收数据包
3.添加过滤器接收数据包的实例
这一节的实例程序所实现的功能及运行效果与上一节相似,只是其中添加了限定数据包捕获长度与只捕获TCP协议数据包的过滤要求。
实例源代码如下,完整的代码见[ch10/filter_cap工程]:
defne WIN32
defne HAVE_REMOTE
include<stdio.h>
include"pcap.h"
include<winsock.h>
/捕获数据包回调函数的原型/
void packet_handler(u_char*param,
const struct pcap_pkthdrheader,const u_charpkt_data);
int main(int argc,char**argv)
{
pcap_t*adhandle;
u_int netmask;
struct bpf_program fcode;
……
/打开本机一个合适的网络设备/
……
if(d->addresses!=NULL)
//获取接口第一个地址的掩码
netmask=((struct sockaddr_in*)
(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
//如果这个接口没有地址,那么我们假设这个接口在C类网络中
netmask=0xffffff;
/编译过滤器为接收TCP协议的数据包/
if(pcap_compile(adhandle,&fcode,"tcp",1,netmask)<0)
{//处理错误
……
}
/设置过滤器/
if(pcap_setflter(adhandle,&fcode)<0)
{//处理错误
……
}
/开始捕获/
pcap_loop(adhandle,0,packet_handler,NULL);
/释放资源/
pcap_freecode(&fcode);
return 0;
}
/每次捕获到数据包时,自动调用该回调函数/
void packet_handler(u_char*param,
const struct pcap_pkthdrheader,const u_charpkt_data)
{
struct tm*ltime;
char timestr[16];
time_t local_tv_sec;
//将时间戳转换成可识别的格式
local_tv_sec=header->ts.tv_sec;
ltime=localtime(&local_tv_sec);
strftime(timestr,sizeof timestr,"%H:%M:%S",ltime);
//打印数据包的捕获时间与数据包的捕获长度与原始长度
printf("%s,%.6d capture len:%d,len:%d\n",
timestr,header->ts.tv_usec,
header->caplen,header->len);
}
实例程序的运行结果如图10-3所示。
图 10-3 添加过滤器接收数据包(捕获长度设置为80)
从图10-3所示中可见,数据包的实际捕获长度不会超过80字节(capture len),即使数据包的实际长度(len)远远大于80字节。