本文转自http://eslxf.blog.51cto.com/918801/213826
1.6.2 发送队列方式的接口实现
在下列两种发送方式都不适用的情况下:
Ø
应用软件每次发送一个数据包一次;
Ø
应用软件每次发送一个数据包大于一次,次数预先设定;
为了能够发送大量的数据包,
WinPcap
提供了基于发送队列发送的方式。通过发送队列提供了一种高级的,强大的,结构更优的方式来发送一组数据包。
库
wpcap.dll
中与发送队列相关的函数所使用的
pcap_send_queue
结构体,是存储原始数据包(将被pcap_sendqueue_transmit函数发送到网络上的数据包)的数据结构,具体定义如下:
{
u_int maxlen; //
队列最大长度
(
字节数
)
,描述了
buffer
成员的容量
u_int len; //
队列当前的字节数
char *buffer; //
储存待发数据包的缓冲区
};
typedef
struct pcap_send_queue pcap_send_queue;
1.6.2.1
pcap_sendqueue_alloc
与pcap_sendqueue_destroy函数
发送队列通过调用
pcap_sendqueue_alloc
函数创建,并且需要指定队列的大小。函数的原型如下:
pcap_send_queue* pcap_sendqueue_alloc(u_int memsize);
函数分配存储发送数据包队列的内存空间
,发送队列是一个容器,它能容纳不同数量的数据包,这些数据包将被
pcap_sendqueue_transmit
函数发送到网络上。其中参数
memsize
是队列的大小(以字节为单位),决定了发送队列能存储数据包的最大容量。
函数成功返回所分配队列的内存指针,否则返回
NULL
。
函数的具体实现如下:
pcap_send_queue* pcap_sendqueue_alloc(u_int memsize)
{
pcap_send_queue *tqueue;
/*
分配发送队列结构体的内存空间
*/
tqueue =
(pcap_send_queue*)malloc(sizeof(pcap_send_queue));
if(tqueue == NULL){
return NULL;
}
/*
分配发送队列
buffer
成员的内存空间
*/
tqueue->buffer = (char*)malloc(memsize);
if(tqueue->buffer == NULL){
free(tqueue);
return NULL;
}
tqueue->maxlen = memsize; //
设置发送队列的最大存储空间
tqueue->len = 0; //
初始化发送队列的已用存储空间
return tqueue;
注意内存空间大小
memsize
参数应该包括每个数据包的头信息(
struct pcap_pkthdr
)
空间,使用示例如下:
squeue=pcap_sendqueue_alloc(
(unsigned int)((MaxPacketLen+sizeof(struct pcap_pkthdr))*npacks) );
当发送队列不再需要时,需要使用 pcap_sendqueue_destroy函数来释放它所占用的内存,该函数原型如下:
void pcap_sendqueue_destroy(pcap_send_queue *queue);
该函数销毁一个发送队列,释放与队列相关的所有内存
空间
。
函数的具体实现如下:
void
pcap_sendqueue_destroy(pcap_send_queue* queue)
{
free(queue->buffer);
//
释放发送队列buffer成员的内存空间
free(queue);
//
释放发送队列结构体的空间
}
1.6.2.2
pcap_sendqueue_queue函数
一旦发送队列被创建,就可以通过pcap_sendqueue_queue函数将数据包添加到发送队列中,该函数把一个数据包添加到queue参数所指的发送队列的尾部。函数原型如下:
int pcap_sendqueue_queue(pcap_send_queue *queue,
const struct pcap_pkthdr *pkt_header,
const u_char *pkt_data)
其中参数queue就是调用pcap_sendqueue_alloc函数所分配的发送队列,参数pkt_header为WinPcap为待发数据包所附加的数据头信息,用来说明数据包的长度与发送时间戳,参数pkt_data为待发数据包。
函数成功返回0,否则返回-1。
结构体
pcap_pkthdr
包含数据包的时间戳和长度信息,结构体
pcap_pkthdr
的具体定义如下:
struct timeval ts;
//
时间戳
bpf_u_int32 caplen;
//
所捕获数据包的长度
bpf_u_int32 len;
//
数据包原始长度
其中结构体timeval的具体定义分别如下:
long tv_sec;
//
秒数
long tv_usec;
//
微秒
函数pcap_sendqueue_queue的具体实现如下:
int
pcap_sendqueue_queue(pcap_send_queue* queue,
const
struct pcap_pkthdr *pkt_header,
const
u_char *pkt_data)
{
/*
发送队列的容量不够,函数返回-1*/
if(queue->len + sizeof(struct pcap_pkthdr) +
pkt_header->caplen > queue->maxlen)
{
return -1;
}
/*
复制数据包头信息pcap_pkthdr*/
memcpy(queue->buffer + queue->len, pkt_header, sizeof(struct pcap_pkthdr));
queue->len += sizeof(struct pcap_pkthdr);
/*
复制数据包
*/
memcpy(queue->buffer + queue->len, pkt_data, pkt_header->caplen);
queue->len += pkt_header->caplen;
return 0;
1.6.2.3
pcap_sendqueue_transmit函数
函数pcap_sendqueue_transmit用来发送一个发送队列,该函数原型如下:
u_int pcap_sendqueue_transmit(pcap_t *p,
pcap_send_queue *queue,int sync)
该函数发送一个原始数据包的队列到网络。参数p指向一个适配器,数据包将通过该适配器发送,参数queue指向一个容纳待发送数据包的pcap_send_queue结构体(参见 pcap_sendqueue_alloc与 pcap_sendqueue_queue函数),参数sync决定了发送操作是否是同步的;如果不为0,根据时间戳发送数据包,否则不根据时间戳,而是尽所能的快速发送各数据包。
函数返回值为实际所发送的字节数,如果该值小于希望发送的大小,那么发送过程中出现了错误。错误可能由驱动程序/适配器的问题或冲突的/假的发送队列导致。
函数的具体实现代码如下:
u_int pcap_sendqueue_transmit(pcap_t *p,
pcap_send_queue* queue, int sync)
{
u_int res;
DWORD error;
int errlen;
if (p->adapter==NULL)
{
sprintf(p->errbuf,
"Cannot transmit a queue to an offline capture
or to a TurboCap port"
);
return 0;
}
//
发送数据包队列
res = PacketSendPackets(p->adapter,
queue->buffer,
queue->len,
(BOOLEAN)sync);
if(res != queue->len)
{
//
错误,记录错误信息
error = GetLastError();
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,NULL,
error,0,p->errbuf,PCAP_ERRBUF_SIZE,NULL);
errlen = strlen(p->errbuf);
if (errlen >= 2) {
p->errbuf[errlen - 1] = '/0';
p->errbuf[errlen - 2] = '/0';
}
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
"Error opening adapter: %s", p->errbuf);
}
return res;
请注意,使用pcap_sendqueue_transmit函数要比pcap_sendpacket函数来发送一系列数据更加有效率,因为发送队列保存在内核层驱动程序的缓冲区中,因此,减少了上下文交换的次数,获得更好的吞吐量。
同时请注意第三个参数sync。如果为非零值,那么发送过程将是同步进行,也就是说,只有与时间戳相符的数据包才会被处理。该同步操作由内核驱动程序“忙等待(busy wait)”循环来实现,所以这个操作需要消耗大量的CPU资源。尽管这个操作对CPU的要求很高,但它对数据包发送的处理结通常是很精确的(通常在可达数微秒左右,或更小)。 如此一个精度是采用pcap_sendpacket函数不可能达到的。