極其常用的Ping命令通過向電腦發送ICMP Echo請求報文並且監聽回應報文的返回,以校正與遠端電腦或本機電腦的串連。
使用ICMP.DLL實現Ping
在Windows平台編程中實現Ping的一個最簡單方法是調用ICMP.DLL這個動態連結程式庫,引用ICMP.DLL中的三個函數即可:
| HANDLE IcmpCreateFile(void); |
這個函數開啟個ICMP Echo請求能使用的控制代碼;
| BOOL IcmpCloseHandle(HANDLE IcmpHandle); |
這個函數關閉由IcmpCreateFile開啟的控制代碼;
DWORD IcmpSendEcho(
HANDLE IcmpHandle, // IcmpCreateFile開啟的控制代碼
IPAddr DestinationAddress, //Echo請求的目的地址
LPVOID RequestData, //發送資料buffer
WORD RequestSize, //發送資料長度
PIP_OPTION_INFORMATION RequestOptions, // IP_OPTION_INFORMATION指標
LPVOID ReplyBuffer, //接收回複buffer
DWORD ReplySize, //接收回複buffer大小
DWORD Timeout //等待逾時
); |
這個函數發送Echo請求並等待回複或逾時。
把這個函數和相關資料封裝成一個類CPing,CPing類的標頭檔如下:
class CPing
{
public:
CPing();
~CPing();
BOOL Ping(char* strHost);
private:
// ICMP.DLL 匯出函數指標
HANDLE (WINAPI *pIcmpCreateFile)(VOID);
BOOL (WINAPI *pIcmpCloseHandle)(HANDLE);
DWORD (WINAPI *pIcmpSendEcho)(HANDLE,DWORD,LPVOID,WORD,PIPINFO,LPVOID,DWORD,DWORD);
HANDLE hndlIcmp; // 載入ICMP.DLL庫控制代碼
BOOL bValid; //是否構造(獲得ICMP.DLL匯出函數指標和初始化WinSock)成功
}; |
CPing類的建構函式獲得ICMP.DLL中匯出函數的指標並初始化WinSock:
CPing::CPing()
{
bValid = FALSE;
WSADATA wsaData;
int nRet;
// 動態載入ICMP.DLL
hndlIcmp = LoadLibrary("ICMP.DLL");
if (hndlIcmp == NULL)
{
::MessageBox(NULL, "Could not load ICMP.DLL", "Error:", MB_OK);
return;
}
// 獲得ICMP.DLL中匯出函數指標
pIcmpCreateFile = (HANDLE (WINAPI *)(void))GetProcAddress((HMODULE)hndlIcmp,"IcmpCreateFile");
pIcmpCloseHandle = (BOOL (WINAPI *)(HANDLE))GetProcAddress((HMODULE)hndlIcmp,"IcmpCloseHandle");
pIcmpSendEcho = (DWORD (WINAPI *)(HANDLE,DWORD,LPVOID,WORD,PIPINFO,LPVOID,DWORD,DWORD))
GetProcAddress((HMODULE)hndlIcmp,"IcmpSendEcho");
// 檢查所有的指標
if (pIcmpCreateFile == NULL || pIcmpCloseHandle == NULL ||pIcmpSendEcho == NULL)
{
::MessageBox(NULL, "Error loading ICMP.DLL", "Error:", MB_OK);
FreeLibrary((HMODULE)hndlIcmp);
return;
}
// 初始化WinSock
nRet = WSAStartup(0x0101, &wsaData );
if (nRet)
{
::MessageBox(NULL, "WSAStartup() error:", "Error:", MB_OK);
WSACleanup();
FreeLibrary((HMODULE)hndlIcmp);
return;
}
// 檢查WinSock的版本
if (0x0101 != wsaData.wVersion)
{
::MessageBox(NULL, "No WinSock version 1.1 support found", "Error:", MB_OK);
WSACleanup();
FreeLibrary((HMODULE)hndlIcmp);
return;
}
bValid = TRUE;
} |
CPing類的解構函式完成相反的動作:
CPing::~CPing()
{
WSACleanup();
FreeLibrary((HMODULE)hndlIcmp);
} |
CPing類的Ping函數是最核心的函數,實現真正的ping操作:
int CPing::Ping(char *strHost)
{
struct in_addr iaDest; // Internet地址結構體
LPHOSTENT pHost; // 主機入口結構體指標
DWORD *dwAddress; // IP地址
IPINFO ipInfo; // IP選項結構體
ICMPECHO icmpEcho; // ICMP Echo回複buffer
HANDLE hndlFile; // IcmpCreateFile函數開啟的控制代碼
if (!bValid)
{
return FALSE;
}
//使用inet_addr()以判定ping目標為地址還是名稱
iaDest.s_addr = inet_addr(strHost);
if (iaDest.s_addr == INADDR_NONE)
pHost = gethostbyname(strHost);
else
pHost = gethostbyaddr((const char*) &iaDest, sizeof(struct in_addr),AF_INET);
if (pHost == NULL)
{
return FALSE;
}
// 拷貝IP地址
dwAddress = (DWORD*)(*pHost->h_addr_list);
// 獲得ICMP Echo控制代碼
hndlFile = pIcmpCreateFile();
// 設定發送資訊預設值
ipInfo.Ttl = 255;
ipInfo.Tos = 0;
ipInfo.IPFlags = 0;
ipInfo.OptSize = 0;
ipInfo.Options = NULL;
icmpEcho.Status = 0;
// 請求一個ICMP echo
pIcmpSendEcho(hndlFile, *dwAddress, NULL, 0, &ipInfo, &icmpEcho, sizeof(struct tagICMPECHO), 1000);
//設定結果
iaDest.s_addr = icmpEcho.Source;
if (icmpEcho.Status)
{
return FALSE;
}
// 關閉ICMP Echo控制代碼
pIcmpCloseHandle(hndlFile);
return TRUE;
} |
其中所使用的相關結構體定義為:
typedef struct tagIPINFO
{
u_char Ttl; // TTL
u_char Tos; // 服務類型
u_char IPFlags; // IP標誌
u_char OptSize; // 可選資料大小
u_char *Options; // 可選資料buffer
} IPINFO, *PIPINFO;
typedef struct tagICMPECHO
{
u_long Source; // 源地址
u_long Status; // IP狀態
u_long RTTime; // RTT
u_short DataSize; // 回複資料大小
u_short Reserved; // 保留
void *pData; // 回複資料buffer
IPINFO ipInfo; // 回複IP選項
} ICMPECHO, *PICMPECHO;