首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> C++ 入門

VC++ 實作 Ping 功能:使用 Raw Socket 發送 ICMP 回應請求

     編輯:C++ 入門

前言

在網路程式設計中,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;
}

主流程實作細節

  1. 建立 Raw Socket:socket(AF_INET, SOCK_RAW, IPPROTO_ICMP) 需要管理員權限
  2. 設定超時:使用 setsockopt 設定 SO_SNDTIMEO 和 SO_RCVTIMEO 為 1 秒
  3. 解析主機名:gethostbyname 取得 IP 位址
  4. 循環發送/接收:預設 4 次,每次發送後以 select 等待回應
  5. 統計計算:記錄往返時間、TTL、計算丟包率

測試程式範例

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;
}

常見問題與注意事項

  • 權限問題:Raw Socket 在 Windows 需要管理員權限,Linux 需要 root 權限或 CAP_NET_RAW
  • 防火牆攔截:部分防火牆會封鎖 ICMP 封包,導致 Ping 失敗
  • IPv6 支援:本實作僅支援 IPv4,IPv6 需使用 AF_INET6 和 IPPROTO_ICMPV6
  • 檢查碼驗證:接收端必須重新計算檢查碼驗證封包完整性

總結

透過 Raw Socket 實作 Ping 功能能深入理解 ICMP 協定與網路程式設計底層機制。本文提供的 CPing 類別封裝完整、介面清晰,可直接整合至專案中使用。實際部署時請注意權限與防火牆設定,並可依需求擴充支援 IPv6 或非同步操作模式。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved