首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 實作 ICMP 協議:自製 Ping 工具核心原理與程式碼

     編輯:關於C#

ICMP 與 Ping 基礎知識

  • ICMP (Internet Control Message Protocol) 屬網路層協議,封裝於 IP 封包中,無連接埠概念。
  • Ping 利用 ICMP Echo Request (Type 8) / Echo Reply (Type 0) 檢測主機可達性。
  • IP 封包結構:IP Header (20 bytes) + ICMP Header (8 bytes) + Data。
  • ICMP 封包結構:Type(1) | Code(1) | Checksum(2) | Identifier(2) | Sequence(2) | Data(可變)。

關鍵實作步驟

  1. 建立 Raw Socket:new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)
  2. 手工構造 ICMP 封包(含 Checksum 計算)
  3. 發送:socket.SendTo(buffer, endPoint)
  4. 接收:socket.ReceiveFrom(buffer, ref remoteEP)
  5. 解析回應,計算往返時間 (RTT)

核心類別:IcmpPacket

public class IcmpPacket
{
    public byte Type;           // 8 = Echo Request, 0 = Echo Reply
    public byte Code;           // 通常為 0
    public ushort Checksum;     // 校驗和
    public ushort Identifier;   // 識別碼,對應請求/回應
    public ushort Sequence;     // 序號
    public byte[] Data;         // 承載資料

    public IcmpPacket(byte type, byte code, ushort checksum,
                      ushort id, ushort seq, int dataSize)
    {
        Type = type; Code = code; Checksum = checksum;
        Identifier = id; Sequence = seq;
        Data = new byte[dataSize];
        for (int i = 0; i < dataSize; i++) Data[i] = (byte)'#';
    }

    // 序列化為 byte 陣列
    public int ToBytes(byte[] buffer)
    {
        int offset = 0;
        buffer[offset++] = Type;
        buffer[offset++] = Code;
        BitConverter.GetBytes(Checksum).CopyTo(buffer, offset); offset += 2;
        BitConverter.GetBytes(Identifier).CopyTo(buffer, offset); offset += 2;
        BitConverter.GetBytes(Sequence).CopyTo(buffer, offset); offset += 2;
        Data.CopyTo(buffer, offset); offset += Data.Length;
        return offset;
    }

    // 計算 ICMP Checksum (RFC 1071)
    public static ushort ComputeChecksum(ushort[] buffer)
    {
        uint sum = 0;
        foreach (ushort v in buffer) sum += v;
        sum = (sum >> 16) + (sum & 0xFFFF);
        sum += (sum >> 16);
        return (ushort)(~sum);
    }
}

完整 Ping 發送/接收流程

public void PingHost(string hostNameOrIp)
{
    // 1. 解析目標 IP
    IPHostEntry hostEntry = Dns.GetHostEntry(hostNameOrIp);
    IPAddress targetIp = hostEntry.AddressList[0];
    IPEndPoint remoteEP = new IPEndPoint(targetIp, 0); // ICMP 無連接埠,設 0

    // 2. 建立 Raw Socket (需管理員權限)
    using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp))
    {
        socket.ReceiveTimeout = 3000;

        // 3. 構造請求封包
        var packet = new IcmpPacket(8, 0, 0, 45, 0, 32); // 32 bytes data
        byte[] sendBuf = new byte[packet.Data.Length + 8];
        int len = packet.ToBytes(sendBuf);

        // 計算 checksum
        ushort[] ckBuf = new ushort[(len + 1) / 2];
        Buffer.BlockCopy(sendBuf, 0, ckBuf, 0, len);
        packet.Checksum = IcmpPacket.ComputeChecksum(ckBuf);

        // 重新序列化含 checksum 的封包
        len = packet.ToBytes(sendBuf);

        // 4. 發送並計時
        int start = Environment.TickCount;
        socket.SendTo(sendBuf, len, SocketFlags.None, remoteEP);

        // 5. 接收回應
        byte[] recvBuf = new byte[1024];
        EndPoint senderEP = new IPEndPoint(IPAddress.Any, 0);
        int recvLen = socket.ReceiveFrom(recvBuf, ref senderEP);
        int rtt = Environment.TickCount - start;

        // 6. 簡易驗證:檢查 Type=0, Identifier 匹配
        if (recvLen > 20 && recvBuf[20] == 0) // IP header 20 bytes + ICMP Type
        {
            Console.WriteLine($"Reply from {targetIp}: time={rtt}ms bytes={recvLen}");
        }
        else
        {
            Console.WriteLine("Request timed out or invalid reply.");
        }
    }
}

執行環境與注意事項

  • OS:Windows 2000 Server / XP / 現代 Windows
  • VS:.NET Framework 1.0+ (原文為 2002/2003,現代 .NET 6/7/8 同樣適用)
  • 需管理員權限 才能建立 Raw Socket
  • 防火牆可能封鎖 ICMP,導致 Ping 失敗但 TCP 連線正常
  • 首次 Ping 因 ARP 解析/路由表建立通常較慢,後續較穩定

執行畫面

Ping 成功

Ping 逾時

程式執行介面

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