深度剖析WinPcap之(九)——数据包的发送过程(5)

本文转自http://eslxf.blog.51cto.com/918801/213826

 

 

1.6.2  发送队列方式的接口实现

在下列两种发送方式都不适用的情况下:
Ø 应用软件每次发送一个数据包一次;
Ø 应用软件每次发送一个数据包大于一次,次数预先设定;
为了能够发送大量的数据包, WinPcap 提供了基于发送队列发送的方式。通过发送队列提供了一种高级的,强大的,结构更优的方式来发送一组数据包。
wpcap.dll 中与发送队列相关的函数所使用的 pcap_send_queue 结构体,是存储原始数据包(将被pcap_sendqueue_transmit函数发送到网络上的数据包)的数据结构,具体定义如下:
struct  pcap_send_queue
{
    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_headerWinPcap为待发数据包所附加的数据头信息,用来说明数据包的长度与发送时间戳,参数pkt_data为待发数据包。
函数成功返回0,否则返回-1
结构体 pcap_pkthdr 包含数据包的时间戳和长度信息,结构体 pcap_pkthdr 的具体定义如下:
struct pcap_pkthdr {
struct timeval ts;    // 时间戳
bpf_u_int32 caplen  //  所捕获数据包的长度
bpf_u_int32 len;       // 数据包原始长度
}
其中结构体timeval的具体定义分别如下:
struct 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函数不可能达到的。

你可能感兴趣的:(开源库)