距离上次发博文有两三个星期的时候了,期间看了一些书,如《Effective C++》、《Windows核心编程》。感觉对计算机有了一个新的认识,自己以前对程序的见解是那么肤浅,完全没有操作系统的概念,也没有程序各部分与内存关系的认识。下面开始介绍这个类。
1. 类成员介绍
Code
#pragma once
#include <vector>
#include "pcap.h"
#pragma comment(lib, "wpcap.lib")
#pragma comment(lib, "Packet.lib")
using namespace std;
// 定义回调函数指针
typedef void (*pcap_back)(u_char *, const struct pcap_pkthdr *,
const u_char *);
class CNetPacket
{
public:
CNetPacket(void);
~CNetPacket(void);
// 初始化网络设备
int InitPcap(void);
// 发送数据包
int SendPacket(const UCHAR*, int);
// 返回设备列表的描述
vector<CString> DevMsg(void);
// 选定用于操作的设备
int SetDev(int);
// 传入回调函数地址至此函数,第二个参数为接收包的个数
void GetPacket(pcap_back, int);
private:
char m_errBuf[PCAP_ERRBUF_SIZE];
pcap_t *m_pDevHandle;
pcap_if_t *m_pAllDevs;
};
这个类包括了用于初始化网络的InitPcap,用于返回网络设备信息的DevMsg,用于选择设备的SetDev,用于发送数据包的SendPacket和用于接收数据包的GetPacket。用户必须先调用InitPcap,再调用SetDev,然后才能执行发送或接收。由于接收函数必采用回调方式才能工作,所以这里typedef了一个回调函数的指针void packet_handler(u_char *, const struct pcap_pkthdr *, const u_char *),它的三个参数都是pcap.h里头的回调函数的参数。实际上,从WinPcap到用户的使用共有两次回调的过程,一是WinPcap提供的回调,另一个是封装类的时候提供的回调。
2. 类的实现
Code
#include "StdAfx.h"
#include "NetPacket.h"
CRITICAL_SECTION g_cs; // 全局变量的关键段
pcap_back m_fp; // 回调函数指针的成员变量
void packet_handler(u_char *, const struct pcap_pkthdr *, const u_char *); // 供WinPcap调用的回调函数
CNetPacket::CNetPacket(void)
{
m_pDevHandle = NULL;
m_pAllDevs = NULL;
}
CNetPacket::~CNetPacket(void)
{
if(m_pDevHandle != NULL)
pcap_close(m_pDevHandle);
DeleteCriticalSection(&g_cs);
}
// 初始化网络设备
int CNetPacket::InitPcap(void)
{
InitializeCriticalSection(&g_cs); //初始化关键段
if(pcap_findalldevs(&m_pAllDevs, m_errBuf) == -1)
return -1;
return 1;
}
// 发送数据包,保证线程安全
int CNetPacket::SendPacket(const UCHAR *data, int length)
{
EnterCriticalSection(&g_cs); //进入关键段
if (pcap_sendpacket(m_pDevHandle, // Adapter
data, // buffer with the packet
length // size
) != 0)
{
LeaveCriticalSection(&g_cs); //退出关键段,不然其他线程不能进入
return -1;
}
LeaveCriticalSection(&g_cs);
return 1;
}
// 返回设备列表的描述
vector<CString> CNetPacket::DevMsg(void)
{
pcap_if_t *d;
vector<CString> devMsg;
CString temp;
for(d = m_pAllDevs; d; d = d->next)
{
temp = d->description;
devMsg.push_back(temp);
}
return devMsg;
}
// 选定用于操作的设备
int CNetPacket::SetDev(int devIndex)
{
pcap_if_t *d;
int i;
/* 找到要选择的网卡结构 */
for(d = m_pAllDevs, i = 0; i < devIndex; d = d->next, ++i);
/* 打开选择的网卡 */
if ( (m_pDevHandle = pcap_open_live(d->name, // 设备名称
65536, // portion of the packet to capture.
// 65536 grants that the whole packet will be captured on all the MACs.
1, // 混杂模式
1000, // 读超时为1秒
m_errBuf // error buffer
) ) == NULL)
{
/* 释放网络设备 */
pcap_freealldevs(m_pAllDevs);
return -1;
}
/*释放网络设备*/
pcap_freealldevs(m_pAllDevs);
return 1;
}
// 传入回调函数地址至此函数
void CNetPacket::GetPacket(pcap_back fp, int cnt)
{
m_fp = fp;
/* 开始捕获包 */
pcap_loop(m_pDevHandle, cnt, packet_handler, NULL);
}
/* 对每一个到来的数据包调用该函数 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
m_fp(param, header, pkt_data);
}
SendPacket中使用了关键段保证线程安全,这样子在使用多线程的时候就不会因为网络设备被同时调用而出错。SendPacket的参数包括了一个UCHAR指针及待发送数据的长度,用户使用时把待发送的数组的地址传入参数一,把数组长度传入参数二。GetPacket的参数包括一个函数指针及接收数据包的数目,这里的函数指针就是用户需要被回调的函数的地址。用户使用的时候把函数名(即函数指针)传入参数一,把接收的数据包的个数传入参数二,当有数据包被检测到的时候回调函数就会被执行,当接收的数据包的数目超出cnt时,pcap_loop就会退出(在此之前pcap_loop是没有退出的,它是一个循环并阻塞等待数据包的函数)。
由于本人是从C#转过来的(虽然开始学的是C++,后来由于种种原因开始使用起C#),对于event方式的回调比较熟悉。开始要写C++的回调一直没弄明白,查了一些中文的资料觉得都看得糊里糊涂的,后来看到外国的资料才明白了http://www.newty.de/fpt/callback.html。其实回调的关键就是把需要被回调的函数的地址作为参数传入另一个函数,还有你必须给这个函数定义一个格式,如参数和返回值,这样子回调的时候程序才能根据地址及参数列表从内存中调用被回调的函数。当然,定义函数格式的话可以通过typedef的形式在头文件中定义,也可以在参数列表中直接定义,如void (*p) (...)这样。
3.写在后面
发送函数只调用了比较简单的pcap_sendpacket,而接收也是调用了pcap_loop。可能你也发现了发送还有pcap_sendqueue_queue,接收还有pcap_next_ex,不过对于简单的任务使用前者就足够了。