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 数据包接收的实例 - 图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.1.2 数据包接收的实例 - 图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.1.2 数据包接收的实例 - 图3

图 10-3 添加过滤器接收数据包(捕获长度设置为80)

从图10-3所示中可见,数据包的实际捕获长度不会超过80字节(capture len),即使数据包的实际长度(len)远远大于80字节。