programming/netfx/fcl/System.Net.NetworkInformation.Ping/index.wiki.txt

current previous
1,694 0,0
+
%smdncms%(set-metadata,title,System.Net.NetworkInformation.Ping)
+
%smdncms%(set-metadata,full-title,pingの送信 (System.Net.NetworkInformation.Ping))
+
%smdncms%(set-metadata,keywords,ping.VB.NET,C#,System.Net.NetworkInformation.Ping,Ping.Send)
+
%smdncms%(set-metadata,tags,api/.net,lang/vb,lang/c#)
+
%smdncms%(set-metadata,alternative-document-versions,codelang=cs,codelang=vb)
+

         
+
#msdn(add-reference,System.Net.Ping.dll)
+
#msdn(add-reference,System.Net.Primitives.dll)
+

         
+
System.Net.NetworkInformation名前空間の&msdn(netfx,type,System.Net.NetworkInformation.Ping){Pingクラス};を使うことで、pingを送信することができる。
+

         
+
Pingクラスでは[[Sendメソッド>#Ping.Send]]・[[SendPingAsyncメソッド>#Ping.SendPingAsync]]を使うことでpingが送信できる。 送信先は[[ホスト名またはIPアドレスを文字列として指定>#Ping.Send_hostNameOrAddress]]するか、IPアドレスを[[IPAddressクラスで指定>#Ping.Send_address]]することができる。
+

         
+
ping送信時のタイムアウト時間は[[引数timeout>#Ping.Send_timeout]]、その他のオプションは[[引数options>#PingOptions]]で指定できる。
+

         
+
$samplecode(lang=c#-9.0,copyright-year=2021,Pingクラスを使ってpingを送信する)
+
#code{{
+
using System;
+
using System.Net.NetworkInformation;
+

         
+
using var ping = new Ping();
+

         
+
const int timeoutMilliseconds = 5000;
+
const string targetHost = "example.com";
+

         
+
// 指定したホストに対してpingを送信する
+
Console.Write($"{targetHost}にpingを送信しています...");
+

         
+
var reply = ping.Send(targetHost, timeoutMilliseconds);
+

         
+
if (reply.Status == IPStatus.Success)
+
  Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
+
else
+
  Console.WriteLine($"失敗しました ({reply.Status})");
+
}}
+
$samplecode(lang=vb-14)
+
#code{{
+
Imports System
+
Imports System.Net.NetworkInformation
+

         
+
Class Sample
+
  Shared Sub Main()
+
    Using ping As New Ping()
+
      Const timeoutMilliseconds As Integer = 5000
+
      Const targetHost As String = "example.com"
+

         
+
      ' 指定したホストに対してpingを送信する
+
      Console.Write($"{targetHost}にpingを送信しています...")
+

         
+
      Dim reply = ping.Send(targetHost, timeoutMilliseconds)
+

         
+
      If reply.Status = IPStatus.Success
+
        Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました")
+
      Else
+
        Console.WriteLine($"失敗しました ({reply.Status})")
+
      End If
+
    End Using
+
  End Sub
+
End Class
+
}}
+
$samplecode
+
#prompt(実行結果例){{
+
example.comにpingを送信しています...93.184.216.34から117ミリ秒で応答がありました
+

         
+
localhostにpingを送信しています...127.0.0.1から0ミリ秒で応答がありました
+

         
+
192.168.0.1にpingを送信しています...192.168.0.1から0ミリ秒で応答がありました
+

         
+
192.168.0.99にpingを送信しています...失敗しました (TimedOut)
+
}}
+
$samplecode$
+

         
+
ping応答に関する詳細は戻り値として返される&msdn(netfx,type,System.Net.NetworkInformation.PingReply){PingReply};から参照できる。 応答があった場合、&msdn(netfx,member,System.Net.NetworkInformation.PingReply.Status){PingReply.Statusプロパティ};の値は&msdn(netfx,member,System.Net.NetworkInformation.IPStatus.Success){IPStatus.Success};となり、&msdn(netfx,member,System.Net.NetworkInformation.PingReply.RoundtripTime){PingReply.RoundtripTimeプロパティ};には応答を受信するまでにかかった時間がミリ秒単位で設定される。
+

         
+
ホスト名の解決ができなかった場合など、pingが送信できない場合は例外&msdn(netfx,type,System.Net.NetworkInformation.PingException){PingException};がスローされる。
+

         
+
$remarks
+
.NET Coreおよび.NETランタイムではping送信時にPingExceptionが発生するバグがある。 これは、非英語ロケールのUnix系OS、かつ一般ユーザーで実行した場合に発生する。 詳細:[[#PingException_on_non_EN_locale]]
+
$remarks$
+

         
+

         
+
#adunit
+

         
+

         
+

         
+
*Pingクラス [#Ping]
+
&msdn(netfx,type,System.Net.NetworkInformation.Ping){Pingクラス};を使ったpingの送信、各メソッドの使い方について。
+

         
+
**pingの送信 (Ping.Send) [#Ping.Send]
+
&msdn(netfx,member,System.Net.NetworkInformation.Ping.Send){Ping.Sendメソッド};を使うことでpingを送信することができる。 非同期的に処理する場合は後述の[[Ping.SendPingAsyncメソッド>#Ping.SendPingAsync]]または[[Ping.SendAsyncメソッド>#Ping.SendAsync]]を使用する。
+

         
+
***ホスト名またはIPアドレスを指定した送信 [#Ping.Send_hostNameOrAddress]
+
&msdn(netfx,member,System.Net.NetworkInformation.Ping.Send){Sendメソッド};では、引数``hostNameOrAddress``にホスト名またはIPアドレス(v4またはv6)を文字列形式で指定することにより、その対象に対してpingを送信することができる。
+

         
+
ホストの名前解決に失敗した場合は、例外&msdn(netfx,type,System.Net.NetworkInformation.PingException){PingException};がスローされる。
+

         
+
$samplecode(lang=c#-9.0,copyright-year=2021,ホスト名を指定してpingを送信する)
+
#code{{
+
using System;
+
using System.Net.NetworkInformation;
+

         
+
using var ping = new Ping();
+

         
+
const string targetHost = "non-existent.invalid"; // 存在しないホスト
+
//const string targetHost = "192.168.0.1"; // ホスト名だけでなくIPアドレスを直接指定することもできる
+

         
+
// 指定したホストに対してpingを送信する
+
Console.Write($"{targetHost}にpingを送信しています...");
+

         
+
try {
+
  var reply = ping.Send(targetHost);
+

         
+
  if (reply.Status == IPStatus.Success)
+
    Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
+
  else
+
    Console.WriteLine($"失敗しました ({reply.Status})");
+
}
+
catch (PingException ex) {
+
  Console.Error.WriteLine(ex);
+
}
+
}}
+
$samplecode(lang=vb-14)
+
#code{{
+
Imports System
+
Imports System.Net.NetworkInformation
+

         
+
Class Sample
+
  Shared Sub Main()
+
    Using ping As New Ping()
+
      Const targetHost As String = "non-existent.invalid" ' 存在しないホスト
+
      'Const targetHost As String = "192.168.0.1" ' ホスト名だけでなくIPアドレスを直接指定することもできる
+

         
+
      ' 指定したホストに対してpingを送信する
+
      Console.Write($"{targetHost}にpingを送信しています...")
+

         
+
      Dim reply = ping.Send(targetHost)
+

         
+
      Try
+
        If reply.Status = IPStatus.Success
+
          Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました")
+
        Else
+
          Console.WriteLine($"失敗しました ({reply.Status})")
+
        End If
+
      Catch ex As PingException
+
        Console.Error.WriteLine(ex)
+
      End Try
+
    End Using
+
  End Sub
+
End Class
+
}}
+
$samplecode
+
#prompt(実行結果){{
+
non-existent.invalidにpingを送信しています...System.Net.NetworkInformation.PingException: An exception occurred during a Ping request.
+
 ---> System.Net.Internals.SocketExceptionFactory+ExtendedSocketException (00000005, 0xFFFDFFFF): Name or service not known
+
   at System.Net.Dns.GetHostEntryOrAddressesCore(String hostName, Boolean justAddresses)
+
   at System.Net.Dns.GetHostAddresses(String hostNameOrAddress)
+
   at System.Net.NetworkInformation.Ping.GetAddressAndSend(String hostNameOrAddress, Int32 timeout, Byte[] buffer, PingOptions options)
+
   --- End of inner exception stack trace ---
+
   at System.Net.NetworkInformation.Ping.GetAddressAndSend(String hostNameOrAddress, Int32 timeout, Byte[] buffer, PingOptions options)
+
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress, Int32 timeout, Byte[] buffer, PingOptions options)
+
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress)
+
   at <Program>$.<Main>$(String[] args) in /home/smdn/samplecodes/cs/test.cs:line 12
+
}}
+
$samplecode$
+

         
+
***IPアドレス(IPAddress)を指定した送信 [#Ping.Send_address]
+
既知のIPアドレスに対して送信する場合は、送信先を&msdn(netfx,type,System.Net.IPAddress){IPAddressクラス};として指定することができる。 IPAddressクラスでは、IPv4/IPv6のどちらも扱うことができる。
+

         
+
文字列形式のIPアドレスからIPAddressインスタンスを作成する場合は、&msdn(netfx,member,System.Net.IPAddress.Parse){IPAddress.Parseメソッド};を使う。
+

         
+
$samplecode(lang=c#-9.0,copyright-year=2021,IPアドレスを指定してpingを送信する)
+
#code{{
+
using System;
+
using System.Net;
+
using System.Net.NetworkInformation;
+

         
+
using var ping = new Ping();
+

         
+
// 既知のアドレスからIPAddressを作成する
+
var address = IPAddress.Parse("192.168.0.1"); // IPv4
+
//var address = IPAddress.Parse("::1"); // IPv6
+

         
+
// 指定したアドレスに対してpingを送信する
+
Console.Write($"{address}にpingを送信しています...");
+

         
+
var reply = ping.Send(address);
+

         
+
Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
+
}}
+
$samplecode(lang=vb-14)
+
#code{{
+
Imports System
+
Imports System.Net
+
Imports System.Net.NetworkInformation
+

         
+
Class Sample
+
  Shared Sub Main()
+
    Using ping As New Ping()
+
      ' 既知のアドレスからIPAddressを作成する
+
      Dim address = IPAddress.Parse("192.168.0.1") ' IPv4
+
      'Dim address = IPAddress.Parse("::1") ' IPv6
+

         
+
      ' 指定したアドレスに対してpingを送信する
+
      Console.Write($"{address}にpingを送信しています...")
+

         
+
      Dim reply = ping.Send(address)
+

         
+
      Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました")
+
    End Using
+
  End Sub
+
End Class
+
}}
+
$samplecode
+
#prompt(実行結果例){{
+
192.168.0.1にpingを送信しています...192.168.0.1から0ミリ秒で応答がありました
+
}}
+
$samplecode$
+

         
+

         
+

         
+
***タイムアウトの指定 [#Ping.Send_timeout]
+
引数``timeout``を指定することで、応答に対するタイムアウト時間を''ミリ秒単位''で指定することができる。
+

         
+
タイムアウトした場合、結果として返されるPingReplyの&msdn(netfx,member,System.Net.NetworkInformation.PingReply.Status){Statusプロパティ};の値は&msdn(netfx,member,System.Net.NetworkInformation.IPStatus.TimedOut){IPStatus.TimedOut};となる。 (例外はスローされない)
+

         
+
$samplecode(lang=c#-9.0,copyright-year=2021,タイムアウト時間を指定してpingを送信する)
+
#code{{
+
using System;
+
using System.Net.NetworkInformation;
+

         
+
using var ping = new Ping();
+

         
+
const int timeoutMilliseconds = 3000; // タイムアウト時間[ミリ秒]
+
const string targetHost = "192.168.0.99";
+

         
+
// 指定したホストに対してpingを送信する
+
Console.Write($"{targetHost}にpingを送信しています...");
+

         
+
var reply = ping.Send(targetHost, timeoutMilliseconds);
+

         
+
if (reply.Status == IPStatus.Success)
+
  Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
+
else if (reply.Status == IPStatus.TimedOut)
+
  Console.WriteLine($"{timeoutMilliseconds}ミリ秒以内に応答がありませんでした");
+
else
+
  Console.WriteLine($"失敗しました ({reply.Status})");
+
}}
+
$samplecode(lang=vb-14)
+
#code{{
+
Imports System
+
Imports System.Net.NetworkInformation
+

         
+
Class Sample
+
  Shared Sub Main()
+
    Using ping As New Ping()
+
      Const timeoutMilliseconds As Integer = 3000 ' タイムアウト時間[ミリ秒]
+
      Const targetHost As String = "192.168.0.99"
+

         
+
      ' 指定したホストに対してpingを送信する
+
      Console.Write($"{targetHost}にpingを送信しています...")
+

         
+
      Dim reply = ping.Send(targetHost, timeoutMilliseconds)
+

         
+
      If reply.Status = IPStatus.Success
+
        Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました")
+
      Else If reply.Status = IPStatus.TimedOut
+
        Console.WriteLine($"{timeoutMilliseconds}ミリ秒以内に応答がありませんでした")
+
      Else
+
        Console.WriteLine($"失敗しました ({reply.Status})")
+
      End If
+
    End Using
+
  End Sub
+
End Class
+
}}
+
$samplecode
+
#prompt(実行結果例){{
+
192.168.0.99にpingを送信しています...3000ミリ秒以内に応答がありませんでした
+
}}
+
$samplecode$
+

         
+

         
+

         
+
***ping送信時のオプション (PingOptions) [#PingOptions]
+
引数``options``に&msdn(netfx,type,System.Net.NetworkInformation.PingOptions){PingOptions};を指定することで、ping送信時のオプションを指定できる。
+

         
+
****PingOptions.Ttl [#PingOptions.Ttl]
+
(この項の内容は検証が不十分です)
+

         
+
&msdn(netfx,member,System.Net.NetworkInformation.PingOptions.Ttl){PingOptions.Ttl};を指定すると、ping送信時のTTL('''Time To Live''')値を設定できる。
+

         
+
TTLが超過した(0に達した)場合は、Statusの値が&msdn(netfx,member,System.Net.NetworkInformation.IPStatus.TtlExpired){IPStatus.TtlExpired};のPingReplyが結果として返されるとされている。
+

         
+
$samplecode(lang=c#-9.0,copyright-year=2021,TTLを指定してpingを送信する)
+
#code{{
+
using System;
+
using System.Net.NetworkInformation;
+

         
+
using var ping = new Ping();
+

         
+
const int timeoutMilliseconds = 5000;
+
const string targetHost = "example.com";
+

         
+
Console.Write($"{targetHost}にpingを送信しています...");
+

         
+
var reply = ping.Send(
+
  targetHost,
+
  timeoutMilliseconds,
+
  Array.Empty<byte>(), // ICMPパケットにデータを含めない(nullを指定できないため空の配列を渡す)
+
  new PingOptions() { Ttl = 1 } // TTL=1でpingを送信する
+
);
+

         
+
switch (reply.Status) {
+
  case IPStatus.Success:
+
    Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
+
    break;
+

         
+
  case IPStatus.TtlExpired:
+
    Console.WriteLine($"TTLが超過しました");
+
    break;
+

         
+
  default:
+
    Console.WriteLine($"失敗しました ({reply.Status})");
+
    break;
+
}
+
}}
+
$samplecode$
+

         
+
$remarks
+
Unix系OS、かつ一般ユーザーで実行した場合、IPStatus.TtlExpiredではなく&msdn(netfx,member,System.Net.NetworkInformation.IPStatus.TimedOut){IPStatus.TimedOut};として扱われる模様。 (関連: [[#Ping_on_unix_privileged]])
+
$remarks$
+

         
+

         
+
****PingOptions.DontFragment [#PingOptions.DontFragment]
+
(この項の内容は検証が不十分です)
+

         
+
&msdn(netfx,member,System.Net.NetworkInformation.PingOptions.DontFragment){PingOptions.DontFragment};を``true``にすると、ping送信時にパケットのフラグメンテーション(複数パケットへの分割)を行わないようになる。
+

         
+
DontFragmentが``true``の場合、かつMTU('''Maximum Transmission Unit''')値を超えるパケットを送信しようとした場合は、Statusの値が&msdn(netfx,member,System.Net.NetworkInformation.IPStatus.PacketTooBig){IPStatus.PacketTooBig};のPingReplyが結果として返されるとされている。
+

         
+
$samplecode(lang=c#-9.0,copyright-year=2021,パケットのフラグメンテーションなし・任意長のデータを含めてpingを送信する)
+
#code{{
+
using System;
+
using System.Net.NetworkInformation;
+

         
+
using var ping = new Ping();
+

         
+
const int timeoutMilliseconds = 5000;
+
const string targetHost = "192.168.0.1";
+

         
+
Console.Write($"{targetHost}にpingを送信しています...");
+

         
+
var reply = ping.Send(
+
  targetHost,
+
  timeoutMilliseconds,
+
  new byte[2048], // ICMPパケットに2048バイトのデータを含めて送信する
+
  new PingOptions() { DontFragment = true } // パケットの分割をさせない
+
);
+

         
+
switch (reply.Status) {
+
  case IPStatus.Success:
+
    Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
+
    break;
+

         
+
  case IPStatus.PacketTooBig:
+
    Console.WriteLine($"MTU値を超えるパケットを送信しようとして失敗しました");
+
    break;
+

         
+
  default:
+
    Console.WriteLine($"失敗しました ({reply.Status})");
+
    break;
+
}
+
}}
+
$samplecode$
+

         
+
$remarks
+
Unix系OS、かつ一般ユーザーで実行した場合は、&msdn(netfx,member,System.Net.NetworkInformation.IPStatus.TimedOut){IPStatus.TimedOut};として扱われ、スーパーユーザーで実行した場合は``SocketException("Message too long")``がInnerExceptionにセットされたPingExceptionがスローされる模様。 (関連: [[#Ping_on_unix_privileged]])
+
$remarks$
+

         
+

         
+

         
+
**pingの非同期送信 [#Ping_async]
+
***Ping.SendPingAsync [#Ping.SendPingAsync]
+
``async``/``await``による非同期送信を行う場合は、&msdn(netfx,member,System.Net.NetworkInformation.Ping.SendPingAsync){Send''Ping''Asyncメソッド};が使用できる。 [[Ping.Sendメソッド>#Ping.Send]]と同様に、引数で[[タイムアウト>#Ping.Send_timeout]]や[[オプション(PingOptions)>#PingOptions]]を指定することもできる。
+

         
+
$samplecode(lang=c#-9.0,copyright-year=2021,SendPingAsyncメソッドにより非同期でpingを送信する)
+
#code{{
+
using System;
+
using System.Net.NetworkInformation;
+

         
+
using var ping = new Ping();
+

         
+
const string targetHost = "example.com";
+

         
+
Console.Write($"{targetHost}にpingを送信しています...");
+

         
+
var reply = await ping.SendPingAsync(targetHost);
+

         
+
if (reply.Status == IPStatus.Success)
+
  Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
+
else
+
  Console.WriteLine($"失敗しました ({reply.Status})");
+
}}
+
$samplecode
+
#prompt(実行結果例){{
+
example.comにpingを送信しています...93.184.216.34から117ミリ秒で応答がありました
+
}}
+
$samplecode$
+

         
+
$remarks
+
SendPingAsyncの追加以前から既に[[SendAsyncメソッド>#Ping.SendAsync]]が存在していたため、``Read``/``ReadAsync``など他の同期的/非同期メソッドとは異なる命名となっている。
+
$remarks$
+

         
+
***Ping.SendAsync [#Ping.SendAsync]
+
&msdn(netfx,member,System.Net.NetworkInformation.Ping.SendAsync){SendAsyncメソッド};でもpingの非同期送信が行える。 このメソッドは[[SendPingAsyncメソッド>#Ping.SendPingAsync]]とは異なり、イベントハンドラで非同期処理を行う。
+

         
+
SendAsyncメソッドを呼び出す前に&msdn(netfx,member,System.Net.NetworkInformation.Ping.PingCompleted){PingCompletedイベント};にイベントハンドラを割り当てておくと、SendAsyncメソッドによるping送信が完了した時点でイベントが発生しイベントハンドラが呼び出される。
+

         
+
ping送信の結果はイベントハンドラにて&msdn(netfx,type,System.Net.NetworkInformation.PingCompletedEventArgs){PingCompletedEventArgs};として受け取ることができ、&msdn(netfx,member,System.Net.NetworkInformation.PingCompletedEventArgs.Reply){PingCompletedEventArgs.Replyプロパティ};からPingReplyを参照できる。
+

         
+
$samplecode(lang=c#-9.0,copyright-year=2021,SendAsyncメソッドにより非同期でpingを送信する)
+
#code{{
+
using System;
+
using System.Net.NetworkInformation;
+

         
+
using var ping = new Ping();
+

         
+
var completed = false;
+

         
+
// ping送信が完了したときのイベントハンドラを割り当てる
+
ping.PingCompleted += (sender, e) => {
+
  // PingReplyを取得する
+
  var reply = e.Reply;
+

         
+
  if (reply.Status == IPStatus.Success)
+
    Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました");
+
  else
+
    Console.WriteLine($"失敗しました ({reply.Status})");
+

         
+
  // 完了したことを示すフラグを設定
+
  completed = true;
+
};
+

         
+
const string targetHost = "example.com";
+

         
+
// SendAsyncによるpingの送信を開始する
+
Console.Write($"{targetHost}にpingを送信しています");
+

         
+
ping.SendAsync(targetHost, null);
+

         
+
// 送信が完了するまで"."を表示して待機する
+
while (!completed) {
+
  Console.Write(".");
+
  await System.Threading.Tasks.Task.Delay(50);
+
}
+
}}
+
$samplecode(lang=vb-14)
+
#code{{
+
Imports System
+
Imports System.Net.NetworkInformation
+

         
+
Class Sample
+
  Shared Sub Main()
+
    Using ping As New Ping()
+
      Dim completed = False
+

         
+
      ' ping送信が完了したときのイベントハンドラを割り当てる
+
      AddHandler ping.PingCompleted, Sub(sender, e)
+
        ' PingReplyを取得する
+
        Dim reply = e.Reply
+

         
+
        If reply.Status = IPStatus.Success
+
          Console.WriteLine($"{reply.Address}から{reply.RoundtripTime}ミリ秒で応答がありました")
+
        Else
+
          Console.WriteLine($"失敗しました ({reply.Status})")
+
        End If
+

         
+
        ' 完了したことを示すフラグを設定
+
        completed = True
+
      End Sub
+

         
+
      Const targetHost As String = "example.com"
+

         
+
      ' SendAsyncによるpingの送信を開始する
+
      Console.Write($"{targetHost}にpingを送信しています")
+

         
+
      ping.SendAsync(targetHost, Nothing)
+

         
+
      ' 送信が完了するまで"."を表示して待機する
+
      Do Until completed
+
        Console.Write(".")
+
        System.Threading.Thread.Sleep(50)
+
      Loop
+
    End Using
+
  End Sub
+
End Class
+
}}
+
$samplecode
+
#prompt(実行結果例){{
+
example.comにpingを送信しています....93.184.216.34から115ミリ秒で応答がありました
+
}}
+
$samplecode$
+

         
+

         
+

         
+

         
+
*Pingクラス内部の動作 [#Ping_internals]
+
**Unix系OS上での動作 [#Ping_on_unix]
+
Linux, MacOS, Android等のUnix系OS上でのPingクラスの動作について。
+

         
+
***スーパーユーザーと一般ユーザーでの動作の違い [#Ping_on_unix_privileged]
+
実行ユーザーがroot権限を持つ場合、Pingクラスは''rawソケット``SOCK_RAW``を作成してICMPパケットを送信''する。 一方、一般ユーザーの場合はrawソケットを作成することができないため、代わりに''システムの``ping``コマンドを呼び出すことによってpingを送信''する。 このため、root権限がなくてもPingクラスでpingを送信することができる。
+

         
+
ただし、一般ユーザーで実行した場合は[[Sendメソッド>#Ping.Send]]の''呼び出しのたびにプロセス生成が行われる''ため、その分オーバーヘッドは大きくなる。
+

         
+
ローカルループバックアドレスに対して1000回pingを送信した場合を比較すると次のようになる。
+

         
+
#prompt(emphasize=brace,スーパーユーザーでのベンチマーク結果){{
+
$ LC_ALL=C {{sudo}} dotnet run -c Release
+

         
+
| Method |     Mean |    Error |   StdDev |
+
|------- |---------:|---------:|---------:|
+
|   Send | {{17.87 ms}} | 0.279 ms | 0.261 ms |
+
}}
+

         
+
#prompt(emphasize=brace,一般ユーザーでのベンチマーク結果){{
+
$ LC_ALL=C dotnet run -c Release
+

         
+
| Method |    Mean |    Error |   StdDev |
+
|------- |--------:|---------:|---------:|
+
|   Send | {{2.228 s}} | 0.0331 s | 0.0276 s |
+
}}
+

         
+
#prompt(検証に使った環境等){{
+
BenchmarkDotNet=v0.12.1, OS=ubuntu 20.04
+
Intel Core i5-6402P CPU 2.80GHz (Skylake), 1 CPU, 4 logical and 4 physical cores
+
.NET Core SDK=5.0.201
+
  [Host]     : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT
+
  DefaultJob : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT
+
}}
+

         
+
#code(cs,type=benchmark,title=検証に使ったコード,license=wtfpl,copyright-year=2021){{
+
using System;
+
using System.Net;
+
using System.Net.NetworkInformation;
+
using BenchmarkDotNet.Attributes;
+
using BenchmarkDotNet.Running;
+

         
+
public class PingSendBenchmark {
+
  private const int count = 1_000;
+

         
+
  [Benchmark]
+
  public int Send()
+
  {
+
    using var ping = new Ping();
+
    var ret = 0;
+

         
+
    for (var n = 0; n < count; n++) {
+
      ping.Send(IPAddress.Loopback);
+
      ret = n;
+
    }
+

         
+
    return ret;
+
  }
+

         
+
  static void Main() => BenchmarkRunner.Run<PingSendBenchmark>();
+
}
+
}}
+

         
+

         
+
***非英語ロケールでPingExceptionが発生する問題 [#PingException_on_non_EN_locale]
+
.NET Core/.NETランタイムで発生する問題。 .NETに統合される以前のMonoランタイムでは発生しない。
+

         
+
[[前述のように>#Ping_on_unix_privileged]]一般ユーザーで実行されている場合、Pingクラスはシステムのpingコマンドを呼び出す動作となる。
+

         
+
ここで、pingコマンドの出力は、ディストリビューションの言語パックのインストール状況やロケールの設定(環境変数``LANG``,``LC_MESSAGES``, ``LC_ALL``など)によってはローカライズされた状態となる場合がある。 Pingクラスはローカライズされた出力の解析に失敗し、PingExceptionをスローする。
+

         
+
$samplecode(lang=c#-9.0,copyright-year=2021,target-framework=netcoreapp3.1;net5,非英語ロケールではPing.SendメソッドでPingExceptionが発生する場合がある)
+
#code{{
+
using System;
+
using System.Net.NetworkInformation;
+
using System.Runtime.InteropServices;
+

         
+
Console.WriteLine(RuntimeInformation.FrameworkDescription);
+

         
+
foreach (var envvar in new[] {"LANG", "LC_MESSAGES", "LC_ALL"}) {
+
  Console.WriteLine($"{envvar}={Environment.GetEnvironmentVariable(envvar)}");
+
}
+
Console.WriteLine();
+

         
+
using var ping = new Ping();
+

         
+
var reply = ping.Send("192.168.0.1");
+

         
+
Console.WriteLine($"{reply.Status} {reply.Address}, {reply.RoundtripTime}[ms]");
+
}}
+
$samplecode
+
#prompt(emphasize=brace,実行結果例1){{
+
{{.NET 5.0.4}}
+
LANG={{ja_JP.UTF-8}}
+
LC_MESSAGES=
+
LC_ALL=
+

         
+
Unhandled exception. System.Net.NetworkInformation.PingException: An exception occurred during a Ping request.
+
 ---> System.Net.NetworkInformation.PingException: An exception occurred during a Ping request.
+
   at System.Net.NetworkInformation.Ping.SendWithPingUtility(IPAddress address, Byte[] buffer, Int32 timeout, PingOptions options)
+
   at System.Net.NetworkInformation.Ping.SendPingCore(IPAddress address, Byte[] buffer, Int32 timeout, PingOptions options)
+
   at System.Net.NetworkInformation.Ping.Send(IPAddress address, Int32 timeout, Byte[] buffer, PingOptions options)
+
   --- End of inner exception stack trace ---
+
   at System.Net.NetworkInformation.Ping.Send(IPAddress address, Int32 timeout, Byte[] buffer, PingOptions options)
+
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress, Int32 timeout, Byte[] buffer, PingOptions options)
+
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress)
+
   at <Program>$.<Main>$(String[] args) in /home/smdn/samplecodes/cs/test.cs:line 14
+
}}
+

         
+
#prompt(emphasize=brace,実行結果例2){{
+
{{.NET Core 3.1.13}}
+
LANG=en_US.UTF8
+
LC_MESSAGES={{ja_JP.UTF8}}
+
LC_ALL=
+

         
+
Unhandled exception. System.Net.NetworkInformation.PingException: An exception occurred during a Ping request.
+
 ---> System.Net.NetworkInformation.PingException: An exception occurred during a Ping request.
+
   at System.Net.NetworkInformation.Ping.SendWithPingUtility(IPAddress address, Byte[] buffer, Int32 timeout, PingOptions options)
+
   at System.Net.NetworkInformation.Ping.SendPingCore(IPAddress address, Byte[] buffer, Int32 timeout, PingOptions options)
+
   at System.Net.NetworkInformation.Ping.Send(IPAddress address, Int32 timeout, Byte[] buffer, PingOptions options)
+
   --- End of inner exception stack trace ---
+
   at System.Net.NetworkInformation.Ping.Send(IPAddress address, Int32 timeout, Byte[] buffer, PingOptions options)
+
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress, Int32 timeout, Byte[] buffer, PingOptions options)
+
   at System.Net.NetworkInformation.Ping.Send(String hostNameOrAddress)
+
   at <Program>$.<Main>$(String[] args) in /home/smdn/samplecodes/cs/test.cs:line 14
+
}}
+

         
+
#prompt(emphasize=brace,実行結果例3){{
+
{{Mono 6.12.0.122}} (tarball Mon Feb 22 17:33:28 UTC 2021)
+
LANG={{ja_JP.UTF-8}}
+
LC_MESSAGES=
+
LC_ALL=
+

         
+
Success 192.168.0.1, 10[ms]
+
}}
+
$samplecode$
+

         
+
この問題は、Pingクラスの内部実装が、英語ロケールの出力フォーマットを前提としていることに起因する。
+

         
+

         
+

         
+
この問題は、以下のいずれかの方法で回避できる。
+

         
+
+root権限で実行する
+
+Monoランタイムで実行する
+
+``C``ロケール(あるいは英語)に設定した上で実行する
+
+pingコマンドの出力に影響する日本語等の言語パックをアンインストールする
+

         
+
``C``ロケールを使って回避する場合は、次のように実装することでコードの変更のみで回避できる。
+

         
+
$samplecode(lang=c#-9.0,copyright-year=2021,target-framework=netcoreapp3.1;net5,一時的にLC_ALLをCにしてPingExceptionの発生を回避する)
+
#code{{
+
using System;
+
using System.Net.NetworkInformation;
+

         
+
using var ping = new Ping();
+

         
+
// 環境変数LC_ALLの現在の値を保持する
+
var LC_ALL = Environment.GetEnvironmentVariable("LC_ALL");
+

         
+
try {
+
  // 一時的に環境変数LC_ALLに"C"を設定する
+
  Environment.SetEnvironmentVariable("LC_ALL", "C");
+

         
+
  // pingを送信する
+
  var reply = ping.Send("192.168.0.1");
+

         
+
  Console.WriteLine($"{reply.Status} {reply.Address}, {reply.RoundtripTime}[ms]");
+
}
+
finally {
+
  // 環境変数LC_ALLを以前の値に戻す
+
  Environment.SetEnvironmentVariable("LC_ALL", LC_ALL);
+
}
+
}}
+
$samplecode$
+

         
+

         
+

         
+

         
+
この問題は[[6.0.0のマイルストーン:https://github.com/dotnet/runtime/milestone/69]]に追加されているため、.NET 6までには修正されると見込まれる。
+

         
+
$relevantdocs
+

         
+
-[[Ping.Send() throws PingException on Linux non-English locale · Issue #50363 · dotnet/runtime:https://github.com/dotnet/runtime/issues/50363]]
+
-[[Set LC_ALL=C before running ping process in Ping.Send() by smdn · Pull Request #50696 · dotnet/runtime:https://github.com/dotnet/runtime/pull/50696]]
+

         
+
$relevantdocs$
+

         
+