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

C# Ping 實作三種方式:Process、Ping 類別、Socket 原始封包

     編輯:關於C語言

前言

在 C# 中執行 Ping 有三種主要路徑:呼叫外部 ping.exe、使用 System.Net.NetworkInformation.Ping、自行構造 ICMP Socket。本文比較三者優劣、提供完整程式碼、非同步版本、權限需求與容器化環境注意事項。

方式一:Process 呼叫 ping.exe (不推薦生產環境)


using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public static class PingViaProcess
{
    public static PingResult Ping(string host, int timeoutMs = 3000)
    {
        var psi = new ProcessStartInfo
        {
            FileName = "ping",
            Arguments = $"-n 1 -w {timeoutMs} {host}",
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
            StandardOutputEncoding = System.Text.Encoding.GetEncoding(950) // 繁中系統代碼頁
        };

        using (var proc = Process.Start(psi))
        {
            string output = proc.StandardOutput.ReadToEnd();
            proc.WaitForExit(timeoutMs + 1000);

            if (proc.ExitCode == 0 && Regex.IsMatch(output, @"TTL=\d+"))
                return new PingResult(true, ParseTime(output));
            if (output.Contains("Destination host unreachable")) return new PingResult(false, "無法到達目的主機");
            if (output.Contains("Request timed out")) return new PingResult(false, "逾時");
            if (output.Contains("Unknown host") || output.Contains("Ping request could not find host")) return new PingResult(false, "無法解析主機名稱");
            return new PingResult(false, output);
        }
    }

    private static int ParseTime(string output)
    {
        var m = Regex.Match(output, @"time[=<]\s*(\d+)ms");
        return m.Success ? int.Parse(m.Groups[1].Value) : -1;
    }
}

public readonly struct PingResult
{
    public bool Success { get; }
    public string Message { get; }
    public int RoundtripTimeMs { get; }
    public PingResult(bool ok, string msg, int time = -1) { Success = ok; Message = msg; RoundtripTimeMs = time; }
    public PingResult(bool ok, int time) { Success = ok; Message = ok ? "成功" : "失敗"; RoundtripTimeMs = time; }
    public override string ToString() => Success ? $"成功 ({RoundtripTimeMs}ms)" : Message;
}
  • 缺點:啟動進程開銷大、解析輸出脆弱、需正確代碼頁、容器/精簡映像可能無 ping.exe。

方式二:System.Net.NetworkInformation.Ping (標準作法)


using System;
using System.Net.NetworkInformation;
using System.Threading.Tasks;

public static class PingHelper
{
    private static readonly Ping _ping = new Ping();

    /// 同步 Ping
    public static PingReply SyncPing(string host, int timeout = 3000, byte[] buffer = null, PingOptions options = null)
        => _ping.Send(host, timeout, buffer ?? new byte[32], options ?? new PingOptions(64, true));

    /// 非同步 Ping (適用高併發)
    public static Task AsyncPing(string host, int timeout = 3000)
        => _ping.SendPingAsync(host, timeout, new byte[32], new PingOptions(64, true));

    /// 批次非同步 Ping
    public static async Task PingMultipleAsync(string[] hosts, int timeout = 3000, int maxConcurrency = 50)
    {
        var semaphore = new SemaphoreSlim(maxConcurrency);
        var tasks = hosts.Select(async h =>
        {
            await semaphore.WaitAsync();
            try { return await AsyncPing(h, timeout); }
            finally { semaphore.Release(); }
        });
        return await Task.WhenAll(tasks);
    }

    /// 解析 PingReply 為統一結果
    public static PingResult ToResult(PingReply reply)
    {
        if (reply == null) return new PingResult(false, "無回應");
        return reply.Status == IPStatus.Success
            ? new PingResult(true, reply.RoundtripTime)
            : new PingResult(false, reply.Status.ToString());
    }
}

進階選項

  • PingOptions(ttl, dontFragment):設定 TTL (預設 64)、禁止分片 (MTU 探測)。
  • buffer:自訂承載資料 (預設 32 bytes),可攜帶序號做相關性比對。
  • IPStatus 列舉:Success, TimedOut, DestinationHostUnreachable, DestinationNetworkUnreachable, BadDestination 等。

方式三:Raw Socket ICMP (需管理員/ROOT、跨平台需 P/Invoke)

.NET 6+ System.Net.NetworkInformation.Ping 內部已使用 Raw Socket (Linux 需 CAP_NET_RAW),手寫 Raw Socket 僅供教學/極特殊需求。

// 僅示意關鍵點,完整實作需處理 Checksum、序號、ID、IP 標頭
// Linux: socket(AF_INET, SOCK_RAW, IPPROTO_ICMP) 需 root 或 setcap cap_net_raw+ep
// Windows: 需管理員權限,且部分防火牆攔截 Raw Socket

權限與容器化部署指南

環境Ping 類別Process ping.exeRaw Socket
Windows 桌面/伺服器✅ 正常✅ 有 ping.exe⚠️ 需管理員
Linux (一般使用者)✅ .NET 6+ 內部 setcap✅ 通常有❌ 需 CAP_NET_RAW
Docker (預設)✅ 需 --cap-add=NET_RAW❌ 精簡映像無❌ 需 --cap-add=NET_RAW
Kubernetes✅ SecurityContext capabilities: NET_RAW❌❌

# Dockerfile 範例
FROM mcr.microsoft.com/dotnet/aspnet:8.0
# 安裝 iputils-ping (若需 Process 方式)
RUN apt-get update && apt-get install -y iputils-ping && rm -rf /var/lib/apt/lists/*
# 或直接用 Ping 類別,需啟用 CAP_NET_RAW
# docker run --cap-add=NET_RAW myimage

效能比較 (1000 次 Ping localhost)

方式總耗時CPU記憶體
Process~12 秒高 (進程建立)高
Ping (同步)~1.8 秒低低
Ping (非同步, 50 並發)~0.3 秒低低

最佳實務總結

  1. 首選 Ping.SendPingAsync:非阻塞、高併發、跨平台、標準庫支援。
  2. 批次掃描:配合 SemaphoreSlim 限制並發,避免網路風暴。
  3. 容器部署:務必加入 NET_RAW capability。
  4. 監控系統:記錄 RoundtripTime、Status、時間戳,推送至 Prometheus/InfluxDB。
  5. 避免 Process:除非需相容極舊 .NET Framework 1.x/2.0 且無 Ping 類別。

總結

System.Net.NetworkInformation.Ping 是現代 C# 執行 ICMP Echo 的標準、高效、跨平台方式。搭配非同步 API 與並發控制,可輕鬆支援大規模網路存活監控。容器化環境務必設定 CAP_NET_RAW,否則 Ping 將全部失敗。

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