在網路程式設計中,Ping 是最基礎的連線測試工具。本文將詳細介紹如何在 VC++ 環境下,使用 Raw Socket 實作完整的 Ping 功能,包括 ICMP 封包的建構、檢查碼計算、發送與接收邏輯,以及超時處理機制。
我們封裝一個 CPing 類別,主要包含以下核心方法:
Ping(LPCSTR pstrHost, UINT nRetries):主流程,建立 Raw Socket、解析主機位址、循環發送/接收 ICMP 封包SendEchoRequest(SOCKET, LPSOCKADDR_IN):建構並發送 ICMP Echo Request 封包RecvEchoReply(SOCKET, LPSOCKADDR_IN, u_char*):接收並解析 ICMP Echo Reply 封包WaitForEchoReply(SOCKET):使用 select 實作非阻塞等待機制in_cksum(u_short*, int):計算 ICMP 標頭檢查碼(RFC 1071 演算法)Result(int*, float*, u_char*):取得統計結果(平均延遲、丟包率、TTL)// IP 標頭 (RFC 791)
typedef struct tagIPHDR {
u_char VIHL; // 版本 + 標頭長度
u_char TOS; // 服務類型
short TotLen; // 總長度
short ID; // 識別碼
short FlagOff; // 旗標 + 片段偏移
u_char TTL; // 存活時間
u_char Protocol; // 協定 (ICMP=1)
u_short Checksum; // 標頭檢查碼
struct in_addr iaSrc; // 來源 IP
struct in_addr iaDst; // 目的 IP
} IPHDR, *PIPHDR;
// ICMP 標頭 (RFC 792)
typedef struct tagICMPHDR {
u_char Type; // 類型 (8=Echo Request, 0=Echo Reply)
u_char Code; // 代碼
u_short Checksum; // 檢查碼
u_short ID; // 識別碼
u_short Seq; // 序號
char Data; // 資料起始
} ICMPHDR, *PICMPHDR;
// 完整 Echo Request 封包
typedef struct tagECHOREQUEST {
ICMPHDR icmpHdr;
DWORD dwTime; // 發送時間戳
char cData[REQ_DATASIZE]; // 32 字節測試資料
} ECHOREQUEST, *PECHOREQUEST;
// Echo Reply 封包 (含 IP 標頭)
typedef struct tagECHOREPLY {
IPHDR ipHdr;
ECHOREQUEST echoRequest;
char cFiller[256];
} ECHOREPLY, *PECHOREPLY;
ICMP 封包必須包含正確的檢查碼,否則會被接收端丟棄。我們採用標準的 Internet 檢查碼演算法:
u_short CPing::in_cksum(u_short *addr, int len) {
register int nleft = len;
register u_short *w = addr;
register int sum = 0;
// 以 16 位元為單位累加
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
// 處理奇數位元組
if (nleft == 1) {
u_short u = 0;
*(u_char*)(&u) = *(u_char*)w;
sum += u;
}
// 將進位捲回加總
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
// 取反碼
return (u_short)~sum;
}
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP) 需要管理員權限setsockopt 設定 SO_SNDTIMEO 和 SO_RCVTIMEO 為 1 秒gethostbyname 取得 IP 位址select 等待回應int main(int argc, char *argv[]) {
// 初始化 Winsock
WSADATA wsaData;
WSAStartup(MAKEWORD(1,1), &wsaData);
CPing ping;
bool result = (argc == 2) ? ping.Ping(argv[1]) : ping.Ping("www.baidu.com");
cout << "result : " << result << endl;
if (result) {
int time; u_char ttl; float miss;
ping.Result(&time, &miss, &ttl);
cout << "time : " << time << "ms TTL : " << (int)ttl
<< " miss : " << miss*100 << "%" << endl;
}
return 0;
}
CAP_NET_RAWAF_INET6 和 IPPROTO_ICMPV6透過 Raw Socket 實作 Ping 功能能深入理解 ICMP 協定與網路程式設計底層機制。本文提供的 CPing 類別封裝完整、介面清晰,可直接整合至專案中使用。實際部署時請注意權限與防火牆設定,並可依需求擴充支援 IPv6 或非同步操作模式。