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

C# 實現 ICMP 協議:從零構建 Ping 工具核心邏輯

     編輯:關於C語言

ICMP 協議基礎

  • Internet 控制報文協議,網絡層協議,封裝在 IP 數據包中
  • 無端口號概念,用類型/代碼區分功能(Echo Request=8, Echo Reply=0)
  • 核心用途:錯誤報告、網絡診斷

IP 數據包結構

[IP Header (20 bytes)] [ICMP Header (8 bytes)] [Data (variable)]
  • IP Header:版本、TOS、總長度、標識、標志/片偏移、TTL、協議(1=ICMP)、校驗和、源/目 IP
  • ICMP Header:類型(1)、代碼(1)、校驗和(2)、標識符(2)、序列號(2)

C# 關鍵類:Socket (Raw + Icmp)


Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
  • Raw:原始套接字,允許構造自定義協議頭
  • ProtocolType.Icmp:指定 ICMP 協議
  • 需管理員權限運行

核心實現步驟

1. 構造 ICMP Echo Request 封包


public class IcmpPacket
{
    public byte Type { get; set; }      // 8 = Echo Request
    public byte Code { get; set; }      // 0
    public ushort Checksum { get; set; }
    public ushort Identifier { get; set; }
    public ushort SequenceNumber { get; set; }
    public byte[] Data { get; set; }

    public IcmpPacket(int dataSize = 32)
    {
        Type = 8; Code = 0; Checksum = 0;
        Identifier = (ushort)Process.GetCurrentProcess().Id;
        SequenceNumber = 0;
        Data = new byte[dataSize];
        // 填充模式數據便於識別
        for (int i = 0; i < dataSize; i++) Data[i] = (byte)'#';
    }

    // 序列化為字節數組
    public byte[] ToBytes()
    {
        using (var ms = new MemoryStream())
        using (var bw = new BinaryWriter(ms))
        {
            bw.Write(Type);
            bw.Write(Code);
            bw.Write(Checksum); // 先寫 0,稍後回填
            bw.Write(Identifier);
            bw.Write(SequenceNumber);
            bw.Write(Data);
            var packet = ms.ToArray();
            
            // 計算校驗和 (RFC 1071)
            ushort checksum = ComputeChecksum(packet);
            // 回填校驗和 (大端序)
            packet[2] = (byte)(checksum >> 8);
            packet[3] = (byte)(checksum & 0xFF);
            return packet;
        }
    }

    private static ushort ComputeChecksum(byte[] buffer)
    {
        uint sum = 0;
        for (int i = 0; i < buffer.Length; i += 2)
        {
            ushort word = (ushort)((buffer[i] << 8) | (i + 1 < buffer.Length ? buffer[i + 1] : 0));
            sum += word;
        }
        // 折疊進位
        while ((sum >> 16) > 0)
            sum = (sum & 0xFFFF) + (sum >> 16);
        return (ushort)~sum;
    }
}

2. 發送與接收


var packet = new IcmpPacket(32).ToBytes();
var remoteEP = new IPEndPoint(IPAddress.Parse("1.1.1.1"), 0); // ICMP 無端口

socket.SendTo(packet, remoteEP);

var receiveBuffer = new byte[1024];
EndPoint senderEP = new IPEndPoint(IPAddress.Any, 0);
int received = socket.ReceiveFrom(receiveBuffer, ref senderEP);

// 解析響應:跳過 IP Header (20 bytes) 讀取 ICMP Header
if (received > 20)
{
    byte icmpType = receiveBuffer[20];
    byte icmpCode = receiveBuffer[21];
    // icmpType == 0 表示 Echo Reply
}

3. 計算往返時間 (RTT)


var sw = Stopwatch.StartNew();
socket.SendTo(packet, remoteEP);
int received = socket.ReceiveFrom(receiveBuffer, ref senderEP);
sw.Stop();
Console.WriteLine($"Reply from {senderEP}: time={sw.ElapsedMilliseconds}ms");

完整 Ping 邏輯

  • 循環發送 4 次 (可配置)
  • 設置接收超時 (socket.ReceiveTimeout)
  • 統計:丟包率、最小/最大/平均 RTT
  • 異常處理:權限不足、目標不可達、超時

注意事項

問題解決
需管理員權限清單文件 `` 或以管理員運行
防火牆攔截允許 ICMP 入站/出站規則
校驗和計算必須按 RFC 1071 大端序計算,包含整個 ICMP 封包
IP Header 長度可變解析響應時先讀取 IP Header 首部長度字段 (IHL),再定位 ICMP

生產級替代方案

  • System.Net.NetworkInformation.Ping 類:.NET 內置封裝,無需 Raw Socket 權限,跨平台
  • 示例:new Ping().Send("1.1.1.1") 返回 PingReply 對象
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved