不用回调方法捕获数据包
本讲的范例程序所实现的功能和效果和上一讲的非常相似 (打开适配器并捕获数据包), 但本讲将用 pcap_next_ex() 函数代替上一讲的 pcap_loop() 函数.
pcap_loop() 函数是基于回调的原理来进行数据捕获,这是一种精妙的方法,并且在某些场合中,它是一种很好的选择。然而,处理回调有时候并不实用 -- 它会增加程序的复杂度,特别是在拥有多线程的C++程序中。
可以通过直接调用 pcap_next_ex() 函数来获得一个数据包 -- 只有当编程人员使用了 pcap_next_ex() 函数才能收到数据包。
这个函数的参数和捕获回调函数的参数是一样的 -- 它包含一个网络适配器的描述符和两个可以初始化和返回给用户的指针 (一个指向 pcap_pkthdr 结构体,另一个指向数据报数据的缓冲)。
在下面的程序中,我们会再次用到上一讲中的有关回调方面的代码,只是我们将它放入了main()函数,之后调用 pcap_next_ex()函数。
#include "pcap.h" int main() { pcap_if_t *alldevs; pcap_if_t *d; int inum; int i=0; pcap_t *adhandle; int res; char errbuf[PCAP_ERRBUF_SIZE]; struct tm ltime; char timestr[16]; struct pcap_pkthdr *header; const u_char *pkt_data; time_t local_tv_sec; /* 获取本机设备列表 */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) { fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf); exit(1); } /* 打印列表 */ for(d=alldevs; d; d=d->next) { printf("%d. %s", ++i, d->name); if (d->description) printf(" (%s)\n", d->description); else printf(" (No description available)\n"); } if(i==0) { printf("\nNo interfaces found! Make sure WinPcap is installed.\n"); return -1; } printf("Enter the interface number (1-%d):",i); scanf_s("%d", &inum); if(inum < 1 || inum > i) { printf("\nInterface number out of range.\n"); /* 释放设备列表 */ pcap_freealldevs(alldevs); return -1; } /* 跳转到已选中的适配器 */ for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++); /* 打开设备 */ if ( (adhandle= pcap_open(d->name, // 设备名 65536, // 要捕捉的数据包的部分 // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容 PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式 1000, // 读取超时时间 NULL, // 远程机器验证 errbuf // 错误缓冲池 ) ) == NULL) { fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name); /* 释放设列表 */ pcap_freealldevs(alldevs); return -1; } printf("\nlistening on %s...\n", d->description); /* 释放设备列表 */ pcap_freealldevs(alldevs); /* 获取数据包 */ while((res = pcap_next_ex( adhandle, &header, &pkt_data)) >= 0){ if(res == 0) /* 超时时间到 */ continue; /* 将时间戳转换成可识别的格式 */ local_tv_sec = header->ts.tv_sec; localtime_s(<ime, &local_tv_sec); strftime( timestr, sizeof timestr, "%H:%M:%S", <ime); 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)); return -1; } return 0; }
为什么我们要用 pcap_next_ex() 代替以前的 pcap_next()? 因为 pcap_next() 有一些不好的地方。首先,它效率低下,尽管它隐藏了回调的方式,但它依然依赖于函数 pcap_dispatch(). 第二,它不能检测到文件末尾这个状态(EOF),因此,如果数据包是从文件读取来的,那么它就不那么有用了。
值得注意的是,pcap_next_ex() 在成功,超时,出错或EOF的情况下,会返回不同的值。